Private
Public Access
1
0

Merge branch 'feature/revisedimports' into develop

This commit is contained in:
Sander Roosendaal
2018-07-04 17:18:05 +02:00
37 changed files with 4000 additions and 1627 deletions

1
data.txt Normal file
View File

@@ -0,0 +1 @@
{"distance": 13878, "user_id": 457764, "ranked": false, "weight_class": "L", "verified": false, "workout_type": false, "comments": "\n from speedcoach2v2.15 via rowsandall.com", "heart_rate": {"max": 158, "average": 156}, "source": "rowingdata", "date_utc": "2018-06-30 05:31:01", "time_formatted": "1:21:16.7", "time": 48767, "date": "2018-06-30 07:31:01", "timezone": "Europe/Prague", "type": "water", "id": 33991243, "stroke_data": true}

View File

@@ -3,42 +3,15 @@
# (There is still some stuff defined directly in views.py. Need to # (There is still some stuff defined directly in views.py. Need to
# move that here.) # move that here.)
# Python from rowers.imports import *
import oauth2 as oauth import datetime
import cgi
import requests
import arrow
import requests.auth
import json
import iso8601
from django.utils import timezone
from datetime import datetime
from datetime import timedelta
import time
# 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
import dataprep
import pytz
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 from requests import Request, Session
from utils import myqueue,uniqify,isprorower, custom_exception_handler, NoTokenError
from rowers.types import otwtypes from rowers.types import otwtypes
from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET
)
from rowers.tasks import handle_c2_import_stroke_data from rowers.tasks import handle_c2_import_stroke_data
import django_rq import django_rq
@@ -46,6 +19,18 @@ queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low') queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low')
oauth_data = {
'client_id': C2_CLIENT_ID,
'client_secret': C2_CLIENT_SECRET,
'redirect_uri': C2_REDIRECT_URI,
'autorization_uri': "https://log.concept2.com/oauth/authorize",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'c2token',
'refreshtokenname': 'c2refreshtoken',
'expirydatename': 'tokenexpirydate',
'bearer_auth': True,
'base_url': "https://log.concept2.com/oauth/access_token",
}
# Checks if user has Concept2 tokens, resets tokens if they are # Checks if user has Concept2 tokens, resets tokens if they are
@@ -337,9 +322,9 @@ def createc2workoutdata_as_splits(w):
split_data.append(thisrecord) split_data.append(thisrecord)
try: try:
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S.%f") durationstr = datetime.datetime.strptime(str(w.duration),"%H:%M:%S.%f")
except ValueError: except ValueError:
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S") durationstr = datetime.datetime.strptime(str(w.duration),"%H:%M:%S")
try: try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com' newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
@@ -406,13 +391,18 @@ def createc2workoutdata(w):
stroke_data.append(thisrecord) stroke_data.append(thisrecord)
try: try:
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S.%f") durationstr = datetime.datetime.strptime(str(w.duration),"%H:%M:%S.%f")
except ValueError: except ValueError:
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S") durationstr = datetime.datetime.strptime(str(w.duration),"%H:%M:%S")
workouttype = w.workouttype workouttype = w.workouttype
if workouttype in otwtypes: if workouttype in otwtypes:
workouttype = 'water' workouttype = 'water'
try:
startdatetime = w.startdatetime.isoformat()
except AttributeError:
startdate = datetime.datetime.combine(w.date,datetime.time())
data = { data = {
"type": workouttype, "type": workouttype,
@@ -451,6 +441,7 @@ def do_refresh_token(refreshtoken):
prepped.body+=scope prepped.body+=scope
response = s.send(prepped) response = s.send(prepped)
token_json = response.json() token_json = response.json()
try: try:
thetoken = token_json['access_token'] thetoken = token_json['access_token']
@@ -488,19 +479,8 @@ def get_token(code):
prepped.body+="&scope=" prepped.body+="&scope="
prepped.body+=scope prepped.body+=scope
print prepped.body
response = s.send(prepped) response = s.send(prepped)
with open("media/c2authorize.log","a") as f:
try:
f.write(reponse.status_code+"\n")
f.write(reponse.text+"\n")
f.write(response.json+"\n\n")
except:
pass
token_json = response.json() token_json = response.json()
try: try:
@@ -540,14 +520,14 @@ def make_authorization_url(request):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
# Get workout from C2 ID # Get workout from C2 ID
def get_c2_workout(user,c2id): def get_workout(user,c2id):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
if (r.c2token == '') or (r.c2token is None): if (r.c2token == '') or (r.c2token is None):
s = "Token doesn't exist. Need to authorize" s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s) return custom_exception_handler(401,s) ,0
elif (timezone.now()>r.tokenexpirydate): elif (timezone.now()>r.tokenexpirydate):
s = "Token expired. Needs to refresh." s = "Token expired. Needs to refresh."
return custom_exception_handler(401,s) return custom_exception_handler(401,s),0
else: else:
# ready to fetch. Hurray # ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.c2token) authorizationstring = str('Bearer ' + r.c2token)
@@ -557,7 +537,28 @@ def get_c2_workout(user,c2id):
url = "https://log.concept2.com/api/users/me/results/"+str(c2id) url = "https://log.concept2.com/api/users/me/results/"+str(c2id)
s = requests.get(url,headers=headers) s = requests.get(url,headers=headers)
return s
data = s.json()['data']
splitdata = None
if 'workout' in data:
if 'splits' in data['workout']:
splitdata = data['workout']['splits']
if 'intervals' in data['workout']:
splitdata = data['workout']['intervals']
# Check if workout has stroke data, and get the stroke data
if data['stroke_data']:
res2 = get_c2_workout_strokes(user,c2id)
if res2.status_code == 200:
strokedata = pd.DataFrame.from_dict(res2.json()['data'])
else:
strokedata = pd.DataFrame()
else:
strokedata = pd.DataFrame()
return data,strokedata
# Get stroke data belonging to C2 ID # Get stroke data belonging to C2 ID
def get_c2_workout_strokes(user,c2id): def get_c2_workout_strokes(user,c2id):
@@ -689,7 +690,8 @@ def workout_c2_upload(user,w):
w.save() w.save()
elif (response.status_code == 201 or response.status_code == 200): elif (response.status_code == 201 or response.status_code == 200):
try: try:
s= json.loads(response.text) # s= json.loads(response.text)
s = response.json()
c2id = s['data']['id'] c2id = s['data']['id']
w.uploadedtoc2 = c2id w.uploadedtoc2 = c2id
w.save() w.save()
@@ -699,7 +701,6 @@ def workout_c2_upload(user,w):
c2id = 0 c2id = 0
else: else:
print response.status_code
message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text
c2id = 0 c2id = 0
@@ -713,7 +714,7 @@ def rower_c2_token_refresh(user):
access_token = res[0] access_token = res[0]
expires_in = res[1] expires_in = res[1]
refresh_token = res[2] refresh_token = res[2]
expirydatetime = timezone.now()+timedelta(seconds=expires_in) expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
r.c2token = access_token r.c2token = access_token
@@ -725,3 +726,154 @@ def rower_c2_token_refresh(user):
else: else:
return None return None
# Create workout data from Strava or Concept2
# data and create the associated Workout object and save it
def add_workout_from_data(user,importid,data,strokedata,
source='c2',splitdata=None,
workoutsource='concept2'):
try:
workouttype = data['type']
except KeyError:
workouttype = 'rower'
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'other'
try:
comments = data['comments']
except:
comments = ' '
try:
thetimezone = tz(data['timezone'])
except:
thetimezone = 'UTC'
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['date_utc'])
except KeyError:
rowdatetime = iso8601.parse_date(data['start_date'])
except ParseError:
rowdatetime = iso8601.parse_date(data['date'])
try:
c2intervaltype = data['workout_type']
except KeyError:
c2intervaltype = ''
try:
title = data['name']
except KeyError:
title = ""
try:
t = data['comments'].split('\n', 1)[0]
title += t[:20]
except:
title = 'Imported'
starttimeunix = arrow.get(rowdatetime).timestamp
res = make_cumvalues(0.1*strokedata['t'])
cum_time = res[0]
lapidx = res[1]
unixtime = cum_time+starttimeunix
# unixtime[0] = starttimeunix
seconds = 0.1*strokedata.ix[:,'t']
nr_rows = len(unixtime)
try:
latcoord = strokedata.ix[:,'lat']
loncoord = strokedata.ix[:,'lon']
except:
latcoord = np.zeros(nr_rows)
loncoord = np.zeros(nr_rows)
try:
strokelength = strokedata.ix[:,'strokelength']
except:
strokelength = np.zeros(nr_rows)
dist2 = 0.1*strokedata.ix[:,'d']
try:
spm = strokedata.ix[:,'spm']
except KeyError:
spm = 0*dist2
try:
hr = strokedata.ix[:,'hr']
except KeyError:
hr = 0*spm
pace = strokedata.ix[:,'p']/10.
pace = np.clip(pace,0,1e4)
pace = pace.replace(0,300)
velo = 500./pace
power = 2.8*velo**3
# save csv
# Create data frame with all necessary data to write to csv
df = pd.DataFrame({'TimeStamp (sec)':unixtime,
' Horizontal (meters)': dist2,
' Cadence (stokes/min)':spm,
' HRCur (bpm)':hr,
' longitude':loncoord,
' latitude':latcoord,
' Stroke500mPace (sec/500m)':pace,
' Power (watts)':power,
' DragFactor':np.zeros(nr_rows),
' DriveLength (meters)':np.zeros(nr_rows),
' StrokeDistance (meters)':strokelength,
' DriveTime (ms)':np.zeros(nr_rows),
' StrokeRecoveryTime (ms)':np.zeros(nr_rows),
' AverageDriveForce (lbs)':np.zeros(nr_rows),
' PeakDriveForce (lbs)':np.zeros(nr_rows),
' lapIdx':lapidx,
' ElapsedTime (sec)':seconds
})
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
# Create CSV file name and save data to CSV file
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
# with Concept2
if source=='c2':
try:
totaldist = data['distance']
totaltime = data['time']/10.
except KeyError:
totaldist = 0
totaltime = 0
else:
totaldist = 0
totaltime = 0
id,message = dataprep.save_workout_database(
csvfilename,r,
workouttype=workouttype,
title=title,notes=comments,
workoutsource=workoutsource,
dosummary=True
)
return id,message

View File

@@ -34,7 +34,7 @@ from rowingdata import (
MysteryParser, BoatCoachOTWParser,QuiskeParser, MysteryParser, BoatCoachOTWParser,QuiskeParser,
painsledDesktopParser, speedcoachParser, ErgStickParser, painsledDesktopParser, speedcoachParser, ErgStickParser,
SpeedCoach2Parser, FITParser, fitsummarydata, SpeedCoach2Parser, FITParser, fitsummarydata,
RitmoTimeParser, RitmoTimeParser,KinoMapParser,
make_cumvalues,cumcpdata,ExcelTemplate, make_cumvalues,cumcpdata,ExcelTemplate,
summarydata, get_file_type, summarydata, get_file_type,
) )
@@ -1073,6 +1073,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
return (w.id, message) return (w.id, message)
parsers = { parsers = {
'kinomap': KinoMapParser,
'xls': ExcelTemplate, 'xls': ExcelTemplate,
'rp': RowProParser, 'rp': RowProParser,
'tcx':TCXParser, 'tcx':TCXParser,
@@ -1173,6 +1174,7 @@ def new_workout_from_file(r, f2,
message = None message = None
try: try:
fileformat = get_file_type(f2) fileformat = get_file_type(f2)
print fileformat,'aap'
except IOError: except IOError:
os.remove(f2) os.remove(f2)
message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect." message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect."

244
rowers/imports.py Normal file
View File

@@ -0,0 +1,244 @@
# All the functionality to connect to SportTracks
# Python
import oauth2 as oauth
import cgi
import pytz
import requests
import requests.auth
import json
from django.utils import timezone
from datetime import datetime
from datetime import timedelta
import arrow
import numpy as np
from dateutil import parser
import time
from time import strftime
import dataprep
import math
from math import sin,cos,atan2,sqrt
import os,sys
import urllib
import iso8601
from uuid import uuid4
# 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, make_cumvalues
import pandas as pd
from rowers.models import Rower,Workout,checkworkoutuser
from rowers import types
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
)
from utils import (
NoTokenError, custom_exception_handler, ewmovingaverage,
geo_distance
)
# Splits SportTracks data which is one long sequence of
# [t,[lat,lon],t2,[lat2,lon2] ...]
# to [t,t2,t3, ...], [[lat,long],[lat2,long2],...
def splitstdata(lijst):
t = []
latlong = []
while len(lijst)>=2:
t.append(lijst[0])
latlong.append(lijst[1])
lijst = lijst[2:]
return [np.array(t),np.array(latlong)]
def splituadata(lijst):
t = []
y = []
for d in lijst:
t.append(d[0])
y.append(d[1])
return np.array(t),np.array(y)
def imports_open(user,oauth_data):
r = Rower.objects.get(user=user)
token = getattr(r,oauth_data['tokenname'])
try:
refreshtoken = getattr(r,oauth_data['refreshtokenname'])
except (AttributeError,KeyError):
refreshtoken = None
try:
tokenexpirydate = getattr(r,oauth_data['expirydatename'])
except (AttributeError,KeyError):
tokenexpirydate = None
if (token == '') or (token is None):
s = "Token doesn't exist. Need to authorize"
raise NoTokenError("User has no token")
else:
if tokenexpirydate and timezone.now()>tokenexpirydate:
token = imports_token_refresh(
user,oauth_data,
)
return token
# Refresh token using refresh token
def imports_do_refresh_token(refreshtoken,oauth_data,access_token=''):
client_auth = requests.auth.HTTPBasicAuth(
oauth_data['client_id'],
oauth_data['client_secret']
)
post_data = {"grant_type": "refresh_token",
"client_secret": oauth_data['client_secret'],
"client_id": oauth_data['client_id'],
"refresh_token": refreshtoken,
}
headers = {'user-agent': 'sanderroosendaal',
'Accept': 'application/json',
'Content-Type': oauth_data['content_type']}
if oauth_data['bearer_auth']:
headers['authorization'] = 'Bearer %s' % access_token
baseurl = oauth_data['base_url']
if 'json' in oauth_data['content_type']:
response = requests.post(baseurl,
data=json.dumps(post_data),
headers=headers)
else:
response = requests.post(baseurl,
data=post_data,
headers=headers)
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
try:
refresh_token = token_json['refresh_token']
except KeyError:
refresh_token = refreshtoken
try:
expires_in = int(expires_in)
except (TypeError,ValueError):
expires_in = 0
return [thetoken,expires_in,refresh_token]
# Exchange ST access code for long-lived ST access token
def imports_get_token(
code,oauth_data
):
redirect_uri = oauth_data['redirect_uri']
client_secret = oauth_data['client_secret']
client_id = oauth_data['client_id']
base_uri = oauth_data['base_url']
client_auth = requests.auth.HTTPBasicAuth(
client_id,client_secret
)
post_data = {"grant_type": "authorization_code",
"code": code,
"redirect_uri": redirect_uri,
"client_secret": client_secret,
"client_id": client_id,
}
headers = {'Accept': 'application/json',
'Api-Key': client_id,
'Content-Type': 'application/json',
'user-agent': 'sanderroosendaal'}
if 'json' in oauth_data['content_type']:
response = requests.post(
base_uri,
data=json.dumps(post_data),
headers=headers)
else:
response = requests.post(
base_uri,
data=post_data,
headers=headers)
if response.status_code == 200 or response.status_code == 201:
token_json = response.json()
thetoken = token_json['access_token']
try:
refresh_token = token_json['refresh_token']
except KeyError:
refresh_token = ''
try:
expires_in = token_json['expires_in']
except KeyError:
expires_in = 0
try:
expires_in = int(expires_in)
except (ValueError,TypeError):
expires_in = 0
else:
return [0,0,0]
return [thetoken,expires_in,refresh_token]
# Make authorization URL including random string
def imports_make_authorization_url(oauth_data):
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
state = str(uuid4())
params = {"client_id": oauth_data['client_id'],
"response_type": "code",
"redirect_uri": oauth_data['redirect_uri'],
"scope":"write",
"state":state}
import urllib
url = oauth_data['authorizaton_uri']+urllib.urlencode(params)
return HttpResponseRedirect(url)
# This is token refresh. Looks for tokens in our database, then refreshes
def imports_token_refresh(user,tokenname,refreshtokenname,expirydatename):
r = Rower.objects.get(user=user)
refreshtoken = getattr(r,refreshtokennname)
res = imports_do_refresh_token(refreshtoken)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
setattr(r,tokenname,access_token)
setattr(r,expirydatename,expirydatetime)
setattr(r,refreshtokenname,refresh_token)
r.save()
return r.sporttrackstoken

View File

@@ -1836,10 +1836,12 @@ class RowerExportForm(ModelForm):
'stravaexportas', 'stravaexportas',
'polar_auto_import', 'polar_auto_import',
'c2_auto_export', 'c2_auto_export',
'c2_auto_import',
'mapmyfitness_auto_export', 'mapmyfitness_auto_export',
'runkeeper_auto_export', 'runkeeper_auto_export',
'sporttracks_auto_export', 'sporttracks_auto_export',
'strava_auto_export', 'strava_auto_export',
'strava_auto_import',
'trainingpeaks_auto_export', 'trainingpeaks_auto_export',
] ]

View File

@@ -1,34 +1,6 @@
# All the functionality needed to connect to Runkeeper # All the functionality needed to connect to Runkeeper
from rowers.imports import *
# Python import re
import oauth2 as oauth
import pytz
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,checkworkoutuser
from rowsandall_app.settings import ( from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
@@ -36,56 +8,50 @@ from rowsandall_app.settings import (
RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET,RUNKEEPER_REDIRECT_URI, RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET,RUNKEEPER_REDIRECT_URI,
) )
from utils import geo_distance,ewmovingaverage,NoTokenError, custom_exception_handler oauth_data = {
'client_id': RUNKEEPER_CLIENT_ID,
'client_secret': RUNKEEPER_CLIENT_SECRET,
'redirect_uri': RUNKEEPER_REDIRECT_URI,
'autorization_uri': "https://www.runkeeper.com/opps/authorize",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'runkeepertoken',
'bearer_auth': True,
'base_url': "https://runkeeper.com/apps/token",
}
def splitrunkeeperlatlongdata(lijst,tname,latname,lonname):
t = []
lat = []
lon = []
for d in lijst:
t.append(d[tname])
lat.append(d[latname])
lon.append(d[lonname])
return [np.array(t),np.array(lat),np.array(lon)]
def splitrunkeeperdata(lijst,xname,yname):
x = []
y = []
for d in lijst:
x.append(d[xname])
y.append(d[yname])
return [np.array(x),np.array(y)]
# Checks if user has SportTracks token, renews them if they are expired # Checks if user has SportTracks token, renews them if they are expired
def runkeeper_open(user): def runkeeper_open(user):
r = Rower.objects.get(user=user) return imports_open(user,oauth_data)
if (r.runkeepertoken == '') or (r.runkeepertoken is None):
s = "Token doesn't exist. Need to authorize"
raise NoTokenError("User has no token")
else:
thetoken = r.runkeepertoken
return thetoken
# Exchange access code for long-lived access token # Exchange access code for long-lived access token
def get_token(code): def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET) return imports_get_token(code,oauth_data)
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 # Make authorization URL including random string
def make_authorization_url(request): def make_authorization_url(request):
# Generate a random string for the state parameter return imports_make_authorization_url(oauth_data)
# 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 # Get list of workouts available on Runkeeper
def get_runkeeper_workout_list(user): def get_runkeeper_workout_list(user):
@@ -105,7 +71,7 @@ def get_runkeeper_workout_list(user):
return s return s
# Get workout summary data by Runkeeper ID # Get workout summary data by Runkeeper ID
def get_runkeeper_workout(user,runkeeperid): def get_workout(user,runkeeperid):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
if (r.runkeepertoken == '') or (r.runkeepertoken is None): if (r.runkeepertoken == '') or (r.runkeepertoken is None):
return custom_exception_handler(401,s) return custom_exception_handler(401,s)
@@ -119,7 +85,16 @@ def get_runkeeper_workout(user,runkeeperid):
url = "https://api.runkeeper.com/fitnessActivities/"+str(runkeeperid) url = "https://api.runkeeper.com/fitnessActivities/"+str(runkeeperid)
s = requests.get(url,headers=headers) s = requests.get(url,headers=headers)
return s try:
data = s.json()
except ValueError:
data = {}
strokedata = pd.DataFrame.from_dict({
key: pd.Series(value) for key, value in data.items()
})
return data,strokedata
# Create Workout Data for upload to SportTracks # Create Workout Data for upload to SportTracks
def createrunkeeperworkoutdata(w): def createrunkeeperworkoutdata(w):
@@ -227,8 +202,10 @@ def createrunkeeperworkoutdata(w):
# upload # upload
def getidfromresponse(response): def getidfromresponse(response):
uri = response.headers["Location"] uri = response.headers["Location"]
id = uri[len(uri)-9:]
tester = re.compile('^\/fitnessActivities\/(\d+)$')
id = int(tester.match(uri).group(1))
return int(id) return int(id)
def geturifromid(access_token,id): def geturifromid(access_token,id):
@@ -328,3 +305,183 @@ def workout_runkeeper_upload(user,w):
return message, rkid return message, rkid
return message,rkid return message,rkid
# Create workout from RunKeeper Data
def add_workout_from_data(user,importid,data,strokedata,source='runkeeper',
workoutsource='runkeeper'):
# To Do - add utcoffset to time
workouttype = data['type']
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'other'
try:
comments = data['notes']
except:
comments = ''
try:
utcoffset = tz(data['utcoffset'])
except:
utcoffset = 0
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['start_time'])
except iso8601.ParseError:
try:
rowdatetime = datetime.strptime(data['start_time'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except ValueError:
try:
rowdatetime = parser.parse(data['start_time'])
#rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
rowdatetime = datetime.strptime(data['date'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
starttimeunix = arrow.get(rowdatetime).timestamp
#starttimeunix = mktime(rowdatetime.utctimetuple())
starttimeunix += utcoffset*3600
try:
title = data['name']
except:
title = "Imported data"
res = splitrunkeeperdata(data['distance'],'timestamp','distance')
distance = res[1]
times_distance = res[0]
try:
l = data['path']
res = splitrunkeeperlatlongdata(l,'timestamp','latitude','longitude')
times_location = res[0]
latcoord = res[1]
loncoord = res[2]
except:
times_location = times_distance
latcoord = np.zeros(len(times_distance))
loncoord = np.zeros(len(times_distance))
if workouttype in types.otwtypes:
workouttype = 'rower'
try:
res = splitrunkeeperdata(data['cadence'],'timestamp','cadence')
times_spm = res[0]
spm = res[1]
except KeyError:
times_spm = times_distance
spm = 0*times_distance
try:
res = splitrunkeeperdata(data['heart_rate'],'timestamp','heart_rate')
hr = res[1]
times_hr = res[0]
except KeyError:
times_hr = times_distance
hr = 0*times_distance
# create data series and remove duplicates
distseries = pd.Series(distance,index=times_distance)
distseries = distseries.groupby(distseries.index).first()
latseries = pd.Series(latcoord,index=times_location)
try:
latseries = latseries.groupby(latseries.index).first()
except TypeError:
latseries = 0.0*distseries
lonseries = pd.Series(loncoord,index=times_location)
try:
lonseries = lonseries.groupby(lonseries.index).first()
except TypeError:
lonseries = 0.0*distseries
spmseries = pd.Series(spm,index=times_spm)
spmseries = spmseries.groupby(spmseries.index).first()
hrseries = pd.Series(hr,index=times_hr)
try:
hrseries = hrseries.groupby(hrseries.index).first()
except TypeError:
hrseries = 0*distseries
# Create dicts and big dataframe
d = {
' Horizontal (meters)': distseries,
' latitude': latseries,
' longitude': lonseries,
' Cadence (stokes/min)': spmseries,
' HRCur (bpm)' : hrseries,
}
df = pd.DataFrame(d)
df = df.groupby(level=0).last()
cum_time = df.index.values
df[' ElapsedTime (sec)'] = cum_time
velo = df[' Horizontal (meters)'].diff()/df[' ElapsedTime (sec)'].diff()
df[' Power (watts)'] = 0.0*velo
nr_rows = len(velo.values)
df[' DriveLength (meters)'] = np.zeros(nr_rows)
df[' StrokeDistance (meters)'] = np.zeros(nr_rows)
df[' DriveTime (ms)'] = np.zeros(nr_rows)
df[' StrokeRecoveryTime (ms)'] = np.zeros(nr_rows)
df[' AverageDriveForce (lbs)'] = np.zeros(nr_rows)
df[' PeakDriveForce (lbs)'] = np.zeros(nr_rows)
df[' lapIdx'] = np.zeros(nr_rows)
unixtime = cum_time+starttimeunix
try:
unixtime[0] = starttimeunix
except IndexError:
return (0,'No data to import')
df['TimeStamp (sec)'] = unixtime
dt = np.diff(cum_time).mean()
wsize = round(5./dt)
# velo2 = stravastuff.ewmovingaverage(velo,wsize)
# df[' Stroke500mPace (sec/500m)'] = 500./velo2
df = df.fillna(0)
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
# csvfilename ='media/Import_'+str(importid)+'.csv'
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
id,message = dataprep.save_workout_database(csvfilename,r,
workouttype=workouttype,
workoutsource='runkeeper',
title=title,
notes=comments)
return (id,message)

View File

@@ -1,144 +1,42 @@
# All the functionality to connect to SportTracks # All the functionality to connect to SportTracks
# Python from rowers.imports import *
import oauth2 as oauth import re
import cgi from rowsandall_app.settings import (
import requests C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
import requests.auth STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
import json SPORTTRACKS_CLIENT_SECRET, SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI
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 urllib
import c2stuff
import pytz
# 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,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
from utils import NoTokenError, custom_exception_handler
oauth_data = {
'client_id': SPORTTRACKS_CLIENT_ID,
'client_secret': SPORTTRACKS_CLIENT_SECRET,
'redirect_uri': SPORTTRACKS_REDIRECT_URI,
'autorization_uri': "https://api.sporttracks.mobi/oauth2/authorize",
'content_type': 'application/json',
'tokenname': 'sporttrackstoken',
'refreshtokenname': 'sporttracksrefreshtoken',
'expirydatename': 'sporttrackstokenexpirydate',
'bearer_auth': False,
'base_url': "https://api.sporttracks.mobi/oauth2/token",
}
# Checks if user has SportTracks token, renews them if they are expired # Checks if user has SportTracks token, renews them if they are expired
def sporttracks_open(user): def sporttracks_open(user):
r = Rower.objects.get(user=user) return imports_open(user, oauth_data)
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
s = "Token doesn't exist. Need to authorize"
raise NoTokenError("User has no token")
else:
if (timezone.now()>r.sporttrackstokenexpirydate):
thetoken = rower_sporttracks_token_refresh(user)
else:
thetoken = r.sporttrackstoken
return thetoken
# Refresh ST token using refresh token # Refresh ST token using refresh token
def do_refresh_token(refreshtoken): def do_refresh_token(refreshtoken):
client_auth = requests.auth.HTTPBasicAuth(SPORTTRACKS_CLIENT_ID, SPORTTRACKS_CLIENT_SECRET) return imports_do_refresh_token(refreshtoken, oauth_data)
post_data = {"grant_type": "refresh_token",
"client_secret": SPORTTRACKS_CLIENT_SECRET,
"client_id":SPORTTRACKS_CLIENT_ID,
"refresh_token": refreshtoken,
}
headers = {'user-agent': 'sanderroosendaal',
'Accept': 'application/json',
'Content-Type': 'application/json'}
url = "https://api.sporttracks.mobi/oauth2/token"
response = requests.post(url,
data=json.dumps(post_data),
headers=headers)
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
try:
refresh_token = token_json['refresh_token']
except KeyError:
refresh_token = refreshtoken
try:
expires_in = int(expires_in)
except (TypeError,ValueError):
expires_in = 0
return [thetoken,expires_in,refresh_token]
# Exchange ST access code for long-lived ST access token # Exchange ST access code for long-lived ST access token
def get_token(code): def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(SPORTTRACKS_CLIENT_ID, SPORTTRACKS_CLIENT_SECRET) return imports_get_token(code,oauth_data)
post_data = {"grant_type": "authorization_code",
"code": code,
"redirect_uri": SPORTTRACKS_REDIRECT_URI,
"client_secret": SPORTTRACKS_CLIENT_SECRET,
"client_id":SPORTTRACKS_CLIENT_ID,
}
headers = {'Accept': 'application/json',
'Content-Type': 'application/json'}
url = "https://api.sporttracks.mobi/oauth2/token"
response = requests.post(url,
data=json.dumps(post_data),
headers=headers)
if response.status_code == 200 or response.status_code == 201:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
try:
refresh_token = token_json['refresh_token']
except KeyError:
refresh_token = refreshtoken
try:
expires_in = int(expires_in)
except (ValueError,TypeError):
expires_in = 0
else:
return [0,0,0]
return [thetoken,expires_in,refresh_token]
# Make authorization URL including random string # Make authorization URL including random string
def make_authorization_url(request): def make_authorization_url(request):
# Generate a random string for the state parameter return imports_make_authorization_url(oauth_data)
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4())
params = {"client_id": SPORTTRACKS_CLIENT_ID,
"response_type": "code",
"redirect_uri": SPORTTRACKS_REDIRECT_URI,
"scope":"write",
"state":state}
import urllib
url = "https://api.sporttracks.mobi/oauth2/authorize" +urllib.urlencode(params)
return HttpResponseRedirect(url)
# This is token refresh. Looks for tokens in our database, then refreshes # This is token refresh. Looks for tokens in our database, then refreshes
def rower_sporttracks_token_refresh(user): def rower_sporttracks_token_refresh(user):
@@ -155,6 +53,7 @@ def rower_sporttracks_token_refresh(user):
r.sporttracksrefreshtoken = refresh_token r.sporttracksrefreshtoken = refresh_token
r.save() r.save()
return r.sporttrackstoken return r.sporttrackstoken
# Get list of workouts available on SportTracks # Get list of workouts available on SportTracks
@@ -178,7 +77,7 @@ def get_sporttracks_workout_list(user):
return s return s
# Get workout summary data by SportTracks ID # Get workout summary data by SportTracks ID
def get_sporttracks_workout(user,sporttracksid): def get_workout(user,sporttracksid):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None): if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
return custom_exception_handler(401,s) return custom_exception_handler(401,s)
@@ -195,7 +94,13 @@ def get_sporttracks_workout(user,sporttracksid):
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities/"+str(sporttracksid) url = "https://api.sporttracks.mobi/api/v2/fitnessActivities/"+str(sporttracksid)
s = requests.get(url,headers=headers) s = requests.get(url,headers=headers)
return s data = s.json()
strokedata = pd.DataFrame.from_dict({
key: pd.Series(value) for key, value in data.items()
})
return data,strokedata
# Create Workout Data for upload to SportTracks # Create Workout Data for upload to SportTracks
def createsporttracksworkoutdata(w): def createsporttracksworkoutdata(w):
@@ -223,9 +128,9 @@ def createsporttracksworkoutdata(w):
d[0] = d[1] d[0] = d[1]
t = t.astype(int) t = t.astype(int)
d = d.astype(int) d = d.astype(int)
spm = row.df[' Cadence (stokes/min)'].astype(int) spm = row.df[' Cadence (stokes/min)'].astype(int).values
spm[0] = spm[1] spm[0] = spm[1]
hr = row.df[' HRCur (bpm)'].astype(int) hr = row.df[' HRCur (bpm)'].astype(int).values
haslatlon=1 haslatlon=1
@@ -240,7 +145,7 @@ def createsporttracksworkoutdata(w):
haspower = 1 haspower = 1
try: try:
power = row.df[' Power (watts)'].values power = row.df[' Power (watts)'].astype(int).values
except KeyError: except KeyError:
haspower = 0 haspower = 0
@@ -311,9 +216,12 @@ def createsporttracksworkoutdata(w):
# Obtain SportTracks Workout ID from the response returned on successful # Obtain SportTracks Workout ID from the response returned on successful
# upload # upload
def getidfromresponse(response): def getidfromresponse(response):
t = json.loads(response.text) t = response.json()
uri = t['uris'][0] uri = t['uris'][0]
id = uri[len(uri)-13:len(uri)-5] regex = '.*?sporttracks\.mobi\/api\/v2\/fitnessActivity/(\d+)$'
m = re.compile(regex).match(uri).group(1)
id = int(m)
return int(id) return int(id)
@@ -324,7 +232,8 @@ def workout_sporttracks_upload(user,w):
# ready to upload. Hurray # ready to upload. Hurray
r = w.user r = w.user
thetoken = sporttracks_open(user) res = sporttracks_open(user)
thetoken = res[0]
if (checkworkoutuser(user,w)): if (checkworkoutuser(user,w)):
data = createsporttracksworkoutdata(w) data = createsporttracksworkoutdata(w)
@@ -332,7 +241,7 @@ def workout_sporttracks_upload(user,w):
message = "Data error" message = "Data error"
stid = 0 stid = 0
return message,stid return message,stid
authorizationstring = str('Bearer ' + thetoken) authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring, headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal', 'user-agent': 'sanderroosendaal',
@@ -349,7 +258,7 @@ def workout_sporttracks_upload(user,w):
w.save() w.save()
return message, stid return message, stid
elif (response.status_code == 201 or response.status_code==200): elif (response.status_code == 201 or response.status_code==200):
s= json.loads(response.text) s= response.json()
stid = getidfromresponse(response) stid = getidfromresponse(response)
w.uploadedtosporttracks = stid w.uploadedtosporttracks = stid
w.save() w.save()
@@ -366,3 +275,176 @@ def workout_sporttracks_upload(user,w):
return message,stid return message,stid
return message,stid return message,stid
# Create workout from SportTracks Data, which are slightly different
# than Strava or Concept2 data
def add_workout_from_data(user,importid,data,strokedata,source='sporttracks',
workoutsource='sporttracks'):
try:
workouttype = data['type']
except KeyError:
workouttype = 'other'
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'other'
try:
comments = data['comments']
except:
comments = ''
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['start_time'])
except iso8601.ParseError:
try:
rowdatetime = datetime.datetime.strptime(data['start_time'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
try:
rowdatetime = dateutil.parser.parse(data['start_time'])
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
rowdatetime = datetime.datetime.strptime(data['date'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
starttimeunix = arrow.get(rowdatetime).timestamp
try:
title = data['name']
except:
title = "Imported data"
try:
res = splitstdata(data['distance'])
distance = res[1]
times_distance = res[0]
except KeyError:
try:
res = splitstdata(data['heartrate'])
times_distance = res[0]
distance = 0*times_distance
except KeyError:
return (0,"No distance or heart rate data in the workout")
try:
l = data['location']
res = splitstdata(l)
times_location = res[0]
latlong = res[1]
latcoord = []
loncoord = []
for coord in latlong:
lat = coord[0]
lon = coord[1]
latcoord.append(lat)
loncoord.append(lon)
except:
times_location = times_distance
latcoord = np.zeros(len(times_distance))
loncoord = np.zeros(len(times_distance))
if workouttype in types.otwtypes:
workouttype = 'rower'
try:
res = splitstdata(data['cadence'])
times_spm = res[0]
spm = res[1]
except KeyError:
times_spm = times_distance
spm = 0*times_distance
try:
res = splitstdata(data['heartrate'])
hr = res[1]
times_hr = res[0]
except KeyError:
times_hr = times_distance
hr = 0*times_distance
# create data series and remove duplicates
distseries = pd.Series(distance,index=times_distance)
distseries = distseries.groupby(distseries.index).first()
latseries = pd.Series(latcoord,index=times_location)
latseries = latseries.groupby(latseries.index).first()
lonseries = pd.Series(loncoord,index=times_location)
lonseries = lonseries.groupby(lonseries.index).first()
spmseries = pd.Series(spm,index=times_spm)
spmseries = spmseries.groupby(spmseries.index).first()
hrseries = pd.Series(hr,index=times_hr)
hrseries = hrseries.groupby(hrseries.index).first()
# Create dicts and big dataframe
d = {
' Horizontal (meters)': distseries,
' latitude': latseries,
' longitude': lonseries,
' Cadence (stokes/min)': spmseries,
' HRCur (bpm)' : hrseries,
}
df = pd.DataFrame(d)
df = df.groupby(level=0).last()
cum_time = df.index.values
df[' ElapsedTime (sec)'] = cum_time
velo = df[' Horizontal (meters)'].diff()/df[' ElapsedTime (sec)'].diff()
df[' Power (watts)'] = 0.0*velo
nr_rows = len(velo.values)
df[' DriveLength (meters)'] = np.zeros(nr_rows)
df[' StrokeDistance (meters)'] = np.zeros(nr_rows)
df[' DriveTime (ms)'] = np.zeros(nr_rows)
df[' StrokeRecoveryTime (ms)'] = np.zeros(nr_rows)
df[' AverageDriveForce (lbs)'] = np.zeros(nr_rows)
df[' PeakDriveForce (lbs)'] = np.zeros(nr_rows)
df[' lapIdx'] = np.zeros(nr_rows)
unixtime = cum_time+starttimeunix
unixtime[0] = starttimeunix
df['TimeStamp (sec)'] = unixtime
dt = np.diff(cum_time).mean()
wsize = round(5./dt)
velo2 = ewmovingaverage(velo,wsize)
df[' Stroke500mPace (sec/500m)'] = 500./velo2
df = df.fillna(0)
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
# csvfilename ='media/Import_'+str(importid)+'.csv'
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
id,message = dataprep.save_workout_database(csvfilename,r,
workouttype=workouttype,
title=title,
notes=comments,
workoutsource='sporttracks')
return (id,message)

View File

@@ -1,33 +1,7 @@
# All the functionality needed to connect to Strava # All the functionality needed to connect to Strava
# Python
import oauth2 as oauth
import cgi
import requests
import requests.auth
import json
import yaml
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
import gzip
from scipy import optimize from scipy import optimize
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
from pytz import timezone as tz,utc
# 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
from django_mailbox.models import Message,Mailbox,MessageAttachment from django_mailbox.models import Message,Mailbox,MessageAttachment
@@ -36,76 +10,49 @@ queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low') queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low')
# Project
# from .models import Profile
from rowingdata import rowingdata
import pandas as pd
from rowers.models import Rower,Workout
from rowers.models import checkworkoutuser
import dataprep
from dataprep import columndict from dataprep import columndict
from utils import uniqify,isprorower,myqueue,NoTokenError, custom_exception_handler
from uuid import uuid4
import stravalib import stravalib
from stravalib.exc import ActivityUploadFailed,TimeoutExceeded from stravalib.exc import ActivityUploadFailed,TimeoutExceeded
import iso8601
from iso8601 import ParseError from iso8601 import ParseError
import pytz
import arrow
from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET
)
try: try:
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
except ImportError: except ImportError:
JSONDecodeError = ValueError JSONDecodeError = ValueError
from rowers.imports import *
from utils import geo_distance,ewmovingaverage oauth_data = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
'redirect_uri': STRAVA_REDIRECT_URI,
'autorization_uri': "https://www.strava.com/oauth/authorize",
'content_type': 'application/json',
'tokenname': 'stravatoken',
'refreshtokenname': '',
'expirydatename': '',
'bearer_auth': True,
'base_url': "https://www.strava.com/oauth/token",
}
# Exchange access code for long-lived access token # Exchange access code for long-lived access token
def get_token(code): def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET) return imports_get_token(code, oauth_data)
post_data = {"grant_type": "authorization_code",
"code": code,
"redirect_uri": STRAVA_REDIRECT_URI,
"client_secret": STRAVA_CLIENT_SECRET,
"client_id":STRAVA_CLIENT_ID,
}
headers = {'user-agent': 'sanderroosendaal'}
response = requests.post("https://www.strava.com/oauth/token",
data=post_data,
headers=headers)
try:
token_json = response.json()
thetoken = token_json['access_token']
except (KeyError,JSONDecodeError):
thetoken = 0
return [thetoken]
# Make authorization URL including random string # Make authorization URL including random string
def make_authorization_url(request): def make_authorization_url(request):
# Generate a random string for the state parameter return imports_make_authorization_url(oauth_data)
# Save it for use later to prevent xsrf attacks
state = str(uuid4())
params = {"client_id": STRAVA_CLIENT_ID,
"response_type": "code",
"redirect_uri": STRAVA_REDIRECT_URI,
"scope":"write"}
import urllib
url = "https://www.strava.com/oauth/authorize" +urllib.urlencode(params)
return HttpResponseRedirect(url)
# Get list of workouts available on Strava # Get list of workouts available on Strava
def get_strava_workout_list(user,limit_n=0): def get_strava_workout_list(user,limit_n=0):
@@ -242,10 +189,12 @@ def create_async_workout(alldata,user,stravaid,debug=False):
return 1 return 1
from utils import get_strava_stream from utils import get_strava_stream
# Get a Strava workout summary data and stroke data by ID # Get a Strava workout summary data and stroke data by ID
def get_strava_workout(user,stravaid): def get_workout(user,stravaid):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
if (r.stravatoken == '') or (r.stravatoken is None): if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize" s = "Token doesn't exist. Need to authorize"
@@ -272,12 +221,15 @@ def get_strava_workout(user,stravaid):
distancejson = get_strava_stream(r,'distance',stravaid) distancejson = get_strava_stream(r,'distance',stravaid)
latlongjson = get_strava_stream(r,'latlng',stravaid) latlongjson = get_strava_stream(r,'latlng',stravaid)
try: try:
t = np.array(timejson.json()[0]['data']) t = np.array(timejson.json()[0]['data'])
nr_rows = len(t) nr_rows = len(t)
d = np.array(distancejson.json()[1]['data']) d = np.array(distancejson.json()[1]['data'])
if nr_rows == 0: if nr_rows == 0:
return (0,"Error: Time data had zero length") return (0,"Error: Time data had zero length")
except IndexError: except IndexError:
d = 0*t d = 0*t
# return (0,"Error: No Distance information in the Strava data") # return (0,"Error: No Distance information in the Strava data")
@@ -324,6 +276,7 @@ def get_strava_workout(user,stravaid):
strokelength = velo*60./(spm) strokelength = velo*60./(spm)
strokelength[np.isinf(strokelength)] = 0.0 strokelength[np.isinf(strokelength)] = 0.0
pace = 500./(1.0*velo2) pace = 500./(1.0*velo2)
pace[np.isinf(pace)] = 0.0 pace[np.isinf(pace)] = 0.0
@@ -337,10 +290,11 @@ def get_strava_workout(user,stravaid):
'strokelength':strokelength, 'strokelength':strokelength,
}) })
# startdatetime = datetime.datetime.strptime(startdatetime,"%Y-%m-%d-%H:%M:%S") # startdatetime = datetime.datetime.strptime(startdatetime,"%Y-%m-%d-%H:%M:%S")
return [workoutsummary,df] return [workoutsummary,df]
# Generate Workout data for Strava (a TCX file) # Generate Workout data for Strava (a TCX file)
def createstravaworkoutdata(w,dozip=True): def createstravaworkoutdata(w,dozip=True):
filename = w.csvfilename filename = w.csvfilename
@@ -421,6 +375,144 @@ def handle_stravaexport(f2,workoutname,stravatoken,description='',
return (res.id,message) return (res.id,message)
# Create workout data from Strava or Concept2
# data and create the associated Workout object and save it
def add_workout_from_data(user,importid,data,strokedata,
source='strava',splitdata=None,
workoutsource='strava'):
try:
workouttype = data['type']
except KeyError:
workouttype = 'rower'
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'other'
try:
comments = data['comments']
except:
comments = ' '
try:
thetimezone = tz(data['timezone'])
except:
thetimezone = 'UTC'
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['date_utc'])
except KeyError:
rowdatetime = iso8601.parse_date(data['start_date'])
except ParseError:
rowdatetime = iso8601.parse_date(data['date'])
try:
intervaltype = data['workout_type']
except KeyError:
intervaltype = ''
try:
title = data['name']
except KeyError:
title = ""
try:
t = data['comments'].split('\n', 1)[0]
title += t[:20]
except:
title = 'Imported'
starttimeunix = arrow.get(rowdatetime).timestamp
res = make_cumvalues(0.1*strokedata['t'])
cum_time = res[0]
lapidx = res[1]
unixtime = cum_time+starttimeunix
seconds = 0.1*strokedata.ix[:,'t']
nr_rows = len(unixtime)
try:
latcoord = strokedata.ix[:,'lat']
loncoord = strokedata.ix[:,'lon']
except:
latcoord = np.zeros(nr_rows)
loncoord = np.zeros(nr_rows)
try:
strokelength = strokedata.ix[:,'strokelength']
except:
strokelength = np.zeros(nr_rows)
dist2 = 0.1*strokedata.ix[:,'d']
try:
spm = strokedata.ix[:,'spm']
except KeyError:
spm = 0*dist2
try:
hr = strokedata.ix[:,'hr']
except KeyError:
hr = 0*spm
pace = strokedata.ix[:,'p']/10.
pace = np.clip(pace,0,1e4)
pace = pace.replace(0,300)
velo = 500./pace
power = 2.8*velo**3
# save csv
# Create data frame with all necessary data to write to csv
df = pd.DataFrame({'TimeStamp (sec)':unixtime,
' Horizontal (meters)': dist2,
' Cadence (stokes/min)':spm,
' HRCur (bpm)':hr,
' longitude':loncoord,
' latitude':latcoord,
' Stroke500mPace (sec/500m)':pace,
' Power (watts)':power,
' DragFactor':np.zeros(nr_rows),
' DriveLength (meters)':np.zeros(nr_rows),
' StrokeDistance (meters)':strokelength,
' DriveTime (ms)':np.zeros(nr_rows),
' StrokeRecoveryTime (ms)':np.zeros(nr_rows),
' AverageDriveForce (lbs)':np.zeros(nr_rows),
' PeakDriveForce (lbs)':np.zeros(nr_rows),
' lapIdx':lapidx,
' ElapsedTime (sec)':seconds
})
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
# Create CSV file name and save data to CSV file
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
id,message = dataprep.save_workout_database(
csvfilename,r,
workouttype=workouttype,
title=title,notes=comments,
workoutsource=workoutsource,
dosummary=True
)
return id,message
def workout_strava_upload(user,w): def workout_strava_upload(user,w):
message = "Uploading to Strava" message = "Uploading to Strava"
@@ -436,9 +528,10 @@ def workout_strava_upload(user,w):
tcxfile,tcxmesg = createstravaworkoutdata(w) tcxfile,tcxmesg = createstravaworkoutdata(w)
if tcxfile: if tcxfile:
with open(tcxfile,'rb') as f: with open(tcxfile,'rb') as f:
res,mes = handle_stravaexport(f,w.name, res,mes = handle_stravaexport(
r.stravatoken, f,w.name,
description=w.notes+'\n from '+w.workoutsource+' via rowsandall.com') r.stravatoken,
description=w.notes+'\n from '+w.workoutsource+' via rowsandall.com')
if res==0: if res==0:
message = mes message = mes
w.uploadedtostrava = -1 w.uploadedtostrava = -1

View File

@@ -111,7 +111,7 @@
{% endif %} {% endif %}
{% else %} {% else %}
<div class="grid_1 alpha"> <div class="grid_1 alpha">
<a href="https://runkeeper.com/activity/{{ workout.uploadedtorunkeeper }}"> <a href="https://runkeeper.com/user/{{ rkuserid }}/activity/{{ workout.uploadedtorunkeeper }}">
<img src="/static/img/rkchecked.png" alt="Runkeeper icon" width="60" height="60"></a> <img src="/static/img/rkchecked.png" alt="Runkeeper icon" width="60" height="60"></a>
</div> </div>
{% endif %} {% endif %}

1
rowers/testdata/c2jsonstrokedata.txt vendored Normal file

File diff suppressed because one or more lines are too long

1
rowers/testdata/c2jsonworkoutdata.txt vendored Normal file
View File

@@ -0,0 +1 @@
{"data": {"distance": 13878, "user_id": 457764, "ranked": false, "weight_class": "L", "verified": false, "workout_type": false, "comments": "\n from speedcoach2v2.15 via rowsandall.com", "heart_rate": {"max": 158, "average": 156}, "source": "rowingdata", "date_utc": "2018-06-30 05:31:01", "time_formatted": "1:21:16.7", "time": 48767, "date": "2018-06-30 07:31:01", "timezone": "Europe/Prague", "type": "water", "id": 33991243, "stroke_data": true}}

1720
rowers/testdata/c2strokedata.csv vendored Normal file

File diff suppressed because it is too large Load Diff

1
rowers/testdata/c2workoutlist.txt vendored Normal file

File diff suppressed because one or more lines are too long

1
rowers/testdata/rkstrokes.txt vendored Normal file

File diff suppressed because one or more lines are too long

1
rowers/testdata/rkworkoutslist.txt vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
rowers/testdata/stravaworkoutlist.txt vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
rowers/testdata/tpuploadresponse.txt vendored Normal file
View File

@@ -0,0 +1 @@
[{"EnergyPlanned": null, "ElevationMaximum": null, "HeartRateMinimum": null, "Title": "2ks with focus (6)", "HeartRateMaximum": null, "CaloriesPlanned": null, "WorkoutDay": "2018-07-02T00:00:00-06:00", "VelocityMaximum": null, "StartTimePlanned": null, "PowerAverage": null, "ElevationMinimum": null, "TssCalculationMethod": null, "VelocityAverage": null, "AthleteId": 1696848, "Completed": true, "CadenceAverage": null, "TssActual": null, "IFPlanned": null, "ElevationLoss": null, "HeartRateAverage": null, "Energy": null, "WorkoutType": "Rowing", "TorqueMaximum": null, "Distance": 1328.0, "VelocityPlanned": null, "TssPlanned": null, "TempAvg": null, "TorqueAverage": null, "Calories": null, "ElevationAverage": null, "PowerMaximum": null, "TotalTimePlanned": null, "ElevationGainPlanned": null, "Feeling": null, "DistanceCustomized": null, "IF": null, "TotalTime": 0.10333333333333333, "NormalizedPower": null, "NormalizedSpeed": null, "DistancePlanned": null, "CadenceMaximum": null, "ElevationGain": null, "TempMin": null, "Rpe": null, "StartTime": "2018-07-02T11:24:05-06:00", "Id": 615737179, "TempMax": null}]

View File

@@ -0,0 +1 @@
{"start_datetime": "2018-07-02T17:24:05+00:00", "name": "2ks with focus (6)", "is_default_name": false, "created_datetime": "2018-07-04T09:17:58+00:00", "notes": " \n from strava via rowsandall.com", "updated_datetime": "2018-07-04T09:17:58+00:00", "reference_key": null, "start_locale_timezone": "Europe/Bratislava", "source": "", "source_manufacturer": null, "_links": {"privacy": [{"href": "/v7.1/privacy_option/1/", "id": "1"}], "route": [{"href": "/v7.1/route/2136037651/", "id": "2136037651"}], "documentation": [{"href": "https://developer.underarmour.com/docs/v71_Workout"}], "user": [{"href": "/v7.1/user/109227799/", "id": "109227799"}], "self": [{"href": "/v7.1/workout/2994311008/", "id": "2994311008"}], "activity_type": [{"href": "/v7.1/activity_type/128/", "id": "128"}]}, "has_time_series": true, "is_verified": true, "aggregates": {"active_time_total": 372.0, "distance_total": 1328.000091264, "cadence_max": 11.0, "speed_max": 4.3000016, "speed_min": 1.6666679, "cadence_min": 9.0, "speed_avg": 3.6897879, "cadence_avg": 9.0, "elapsed_time_total": 372.0, "heartrate_avg": 148.0}}

1
rowers/testdata/uastrokes.txt vendored Normal file

File diff suppressed because one or more lines are too long

1
rowers/testdata/uauser.txt vendored Normal file
View File

@@ -0,0 +1 @@
{"last_name": "Roosendaal", "weight": null, "communication": {"promotions": false, "newsletter": false, "system_messages": false}, "height": null, "hobbies": "", "id": 109227799, "date_joined": "2017-03-28T11:32:53+00:00", "first_name": "Sander", "display_name": "Sander Roosendaal", "introduction": "", "display_measurement_system": "metric", "last_login": "2017-03-28T11:32:53+00:00", "location": {"country": "US", "region": "VA", "address": "", "locality": "Ashburn"}, "_links": {"stats": [{"href": "/v7.1/user_stats/109227799/?aggregate_by_period=month", "id": "109227799", "name": "month"}, {"href": "/v7.1/user_stats/109227799/?aggregate_by_period=day", "id": "109227799", "name": "day"}, {"href": "/v7.1/user_stats/109227799/?aggregate_by_period=week", "id": "109227799", "name": "week"}, {"href": "/v7.1/user_stats/109227799/?aggregate_by_period=year", "id": "109227799", "name": "year"}, {"href": "/v7.1/user_stats/109227799/?aggregate_by_period=lifetime", "id": "109227799", "name": "lifetime"}], "privacy": [{"href": "/v7.1/privacy_option/1/", "id": "1", "name": "status_post"}, {"href": "/v7.1/privacy_option/1/", "id": "1", "name": "workout"}, {"href": "/v7.1/privacy_option/0/", "id": "0", "name": "workout_music"}, {"href": "/v7.1/privacy_option/3/", "id": "3", "name": "activity_feed"}, {"href": "/v7.1/privacy_option/0/", "id": "0", "name": "bodymass"}, {"href": "/v7.1/privacy_option/1/", "id": "1", "name": "food_log"}, {"href": "/v7.1/privacy_option/3/", "id": "3", "name": "email_search"}, {"href": "/v7.1/privacy_option/1/", "id": "1", "name": "profile"}, {"href": "/v7.1/privacy_option/1/", "id": "1", "name": "route"}, {"href": "/v7.1/privacy_option/0/", "id": "0", "name": "sleep"}], "image": [{"href": "/v7.1/user_profile_photo/109227799/", "id": "109227799", "name": "user_profile_photo"}], "documentation": [{"href": "https://developer.underarmour.com/docs/v71_User"}], "deactivation": [{"href": "/v7.1/user_deactivation/"}], "user_achievements": [{"href": "/v7.1/user_achievement/?user=109227799"}], "friendships": [{"href": "/v7.1/friendship/?from_user=109227799"}], "workouts": [{"href": "/v7.1/workout/?user=109227799&order_by=-start_datetime"}], "self": [{"href": "/v7.1/user/109227799/", "id": "109227799"}]}, "email": "roosendaalsander@gmail.com", "goal_statement": null, "username": "Sander109227799", "sharing": {"twitter": false, "facebook": false}, "last_initial": "R.", "preferred_language": "en-US", "gender": "M", "time_zone": "Europe/Bratislava", "birthdate": "1972-04-19", "profile_statement": ""}

1
rowers/testdata/uaworkoutlist.txt vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +1,12 @@
# All the functionality needed to connect to Runkeeper # All the functionality needed to connect to Runkeeper
from rowers.imports import *
# Python # Python
import oauth2 as oauth
import cgi
import requests
import requests.auth
import json
from django.utils import timezone
from datetime import datetime
from datetime import timedelta
import numpy as np
from dateutil import parser
import time
import pytz
import math
import gzip import gzip
from math import sin,cos,atan2,sqrt
import os,sys
import urllib
import base64 import base64
from io import BytesIO from io import BytesIO
from time import strftime
# 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
#from django.contrib import messages
# Project
# from .models import Profile
from rowingdata import rowingdata
import pandas as pd
from rowers.models import Rower,Workout,checkworkoutuser
from rowsandall_app.settings import ( from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
@@ -50,116 +22,35 @@ from django_rq import job
import time import time
from async_messages import message_user,messages from async_messages import message_user,messages
oauth_data = {
'client_id': TP_CLIENT_ID,
'client_secret': TP_CLIENT_SECRET,
from utils import geo_distance, NoTokenError,ewmovingaverage, custom_exception_handler 'redirect_uri': TP_REDIRECT_URI,
'autorization_uri': "https://oauth.trainingpeaks.com/oauth/authorize?",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'tptoken',
'refreshtokenname': 'tprefreshtoken',
'expirydatename': 'tptokenexpirydate',
'bearer_auth': False,
'base_url': "https://oauth.trainingpeaks.com/oauth/token",
}
# Checks if user has UnderArmour token, renews them if they are expired # Checks if user has UnderArmour token, renews them if they are expired
def tp_open(user): def tp_open(user):
r = Rower.objects.get(user=user) return imports_open(user, oauth_data)
if (r.tptoken == '') or (r.tptoken is None):
s = "Token doesn't exist. Need to authorize"
raise NoTokenError("User has no token")
else:
if (timezone.now()>r.tptokenexpirydate):
res = do_refresh_token(r.tprefreshtoken)
if res[0] != 0:
r.tptoken = res[0]
r.tprefreshtoken = res[2]
expirydatetime = timezone.now()+timedelta(seconds=res[1])
r.tptokenexpirydate = expirydatetime
r.save()
thetoken = r.tptoken
else:
raise NoTokenError("Refresh token invalid")
else:
thetoken = r.tptoken
return thetoken
# Refresh ST token using refresh token # Refresh ST token using refresh token
def do_refresh_token(refreshtoken): def do_refresh_token(refreshtoken):
client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET) return imports_do_refresh_token(refreshtoken, oauth_data)
post_data = {"grant_type": "refresh_token",
"client_secret": TP_CLIENT_SECRET,
"client_id":TP_CLIENT_KEY,
"refresh_token": refreshtoken,
}
headers = {'user-agent': 'sanderroosendaal',
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
}
url = "https://oauth.trainingpeaks.com/oauth/token"
response = requests.post(url,
data=post_data,
headers=headers)
if response.status_code == 200 or response.status_code == 201:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
try:
refresh_token = token_json['refresh_token']
except KeyError:
refresh_token = refreshtoken
else:
return [0,0,0]
return [thetoken,expires_in,refresh_token]
# Exchange access code for long-lived access token # Exchange access code for long-lived access token
def get_token(code): def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET) return imports_get_token(code, oauth_data)
post_data = {
"client_id":TP_CLIENT_KEY,
"grant_type": "authorization_code",
"code": code,
"redirect_uri":TP_REDIRECT_URI,
"client_secret": TP_CLIENT_SECRET,
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
response = requests.post("https://oauth.trainingpeaks.com/oauth/token",
data=post_data)
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
except KeyError:
thetoken = 0
expires_in = 0
refresh_token = 0
return thetoken,expires_in,refresh_token
# Make authorization URL including random string # Make authorization URL including random string
def make_authorization_url(request): def make_authorization_url(request):
# Generate a random string for the state parameter return imports_make_authorization_url(oauth_data)
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4())
params = {"client_id": TP_CLIENT_KEY,
"response_type": "code",
"redirect_uri": TP_REDIRECT_URI,
"scope": "file:write",
}
url = "https://oauth.trainingpeaks.com/oauth/authorize?" +urllib.urlencode(params)
return HttpResponseRedirect(url)
def getidfromresponse(response): def getidfromresponse(response):
@@ -229,7 +120,6 @@ def uploadactivity(access_token,filename,description='',
data = json.dumps(data), data = json.dumps(data),
headers=headers) headers=headers)
print resp.status_code
if resp.status_code != 200: if resp.status_code != 200:
if settings.DEBUG: if settings.DEBUG:
print resp.status_code print resp.status_code
@@ -237,24 +127,11 @@ def uploadactivity(access_token,filename,description='',
print "" print ""
print headers print headers
print "" print ""
with open("media/tperrors.log","a") as errorlog:
errorlog.write(str(resp.status_code))
errorlog.write("\r\n")
timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+"\r\n")
errorlog.write("\r\n")
errorlog.write(str(resp.reason))
errorlog.write("\r\n")
try:
errorlog.write(str(resp.json()))
except:
pass
errorlog.write("\r\n")
return 0,resp.reason,resp.status_code,headers return 0,resp.reason,resp.status_code,headers
else: else:
return resp.json()[0]["Id"],"ok",200,"" return resp.json()[0]["Id"],"ok",200,""
return 0 return 0,0,0,0
def workout_tp_upload(user,w): def workout_tp_upload(user,w):

View File

@@ -1,152 +1,41 @@
# All the functionality needed to connect to Runkeeper from rowers.imports import *
# Python
import oauth2 as oauth
import cgi
import pytz
import requests
import requests.auth
import json
from django.utils import timezone
from datetime import datetime,timedelta
import arrow
import numpy as np
from dateutil import parser
import time
import math
from math import sin,cos,atan2,sqrt
import os,sys
import urllib
# 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,checkworkoutuser
from rowsandall_app.settings import ( from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, UNDERARMOUR_CLIENT_KEY,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, UNDERARMOUR_CLIENT_SECRET,
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_CLIENT_SECRET, UNDERARMOUR_REDIRECT_URI,
UNDERARMOUR_REDIRECT_URI,UNDERARMOUR_CLIENT_KEY,
) )
from utils import NoTokenError,ewmovingaverage oauth_data = {
'client_id': UNDERARMOUR_CLIENT_KEY,
from utils import geo_distance, custom_exception_handler 'client_secret': UNDERARMOUR_CLIENT_SECRET,
'redirect_uri': UNDERARMOUR_REDIRECT_URI,
'autorization_uri': "https://www.mapmyfitness.com/v7.1/oauth2/uacf/authorize/",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'underarmourtoken',
'refreshtokenname': 'underarmourrefreshtoken',
'expirydatename': 'underarmourtokenexpirydate',
'bearer_auth': True,
'base_url': "https://api.ua.com/v7.1/oauth2/access_token/",
}
# Checks if user has UnderArmour token, renews them if they are expired # Checks if user has UnderArmour token, renews them if they are expired
def underarmour_open(user): def underarmour_open(user):
r = Rower.objects.get(user=user) return imports_open(user,oauth_data)
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
s = "Token doesn't exist. Need to authorize"
raise NoTokenError("User has no token")
else:
if (timezone.now()>r.underarmourtokenexpirydate):
res = do_refresh_token(
r.underarmourrefreshtoken,r.underarmourtoken
)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
r = getrower(request.user)
r.underarmourtoken = access_token
r.underarmourtokenexpirydate = expirydatetime
r.underarmourrefreshtoken = refresh_token
r.save()
thetoken = r.underarmourtoken
else:
thetoken = r.underarmourtoken
return thetoken
# Refresh ST token using refresh token # Refresh ST token using refresh token
def do_refresh_token(refreshtoken,access_token): def do_refresh_token(refreshtoken,access_token):
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET) return imports_do_refresh_token(
post_data = {"grant_type": "refresh_token", refreshtoken,oauth_data,access_token=access_token
"client_secret": UNDERARMOUR_CLIENT_SECRET, )
"client_id":UNDERARMOUR_CLIENT_KEY,
"refresh_token": refreshtoken,
}
headers = {'user-agent': 'sanderroosendaal',
"Api-Key":UNDERARMOUR_CLIENT_KEY,
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'authorization': 'Bearer %s' % access_token}
url = "https://api.ua.com/v7.1/oauth2/access_token/"
response = requests.post(url,
data=post_data,
headers=headers)
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
try:
refresh_token = token_json['refresh_token']
except KeyError:
refresh_token = refreshtoken
return [thetoken,expires_in,refresh_token]
# Exchange access code for long-lived access token # Exchange access code for long-lived access token
def get_token(code): def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET) return imports_get_token(code,oauth_data)
post_data = {
"grant_type": "authorization_code",
"code": code,
"client_secret": UNDERARMOUR_CLIENT_SECRET,
"client_id":UNDERARMOUR_CLIENT_KEY,
}
headers = {
'user-agent': 'sanderroosendaal',
"Api-Key":UNDERARMOUR_CLIENT_KEY,
}
response = requests.post("https://api.ua.com/v7.1/oauth2/access_token/",
data=post_data,
headers=headers)
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
except KeyError:
thetoken = 0
expires_in = 30
refresh_token = ''
return thetoken,expires_in,refresh_token
# Make authorization URL including random string # Make authorization URL including random string
def make_authorization_url(request): def make_authorization_url(request):
# Generate a random string for the state parameter return imports_make_authorization_url(oauth_data)
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4())
params = {"client_id": UNDERARMOUR_CLIENT_KEY,
"response_type": "code",
"redirect_uri": UNDERARMOUR_REDIRECT_URI,
}
url = "https://www.mapmyfitness.com/v7.1/oauth2/uacf/authorize/" +urllib.urlencode(params)
return HttpResponseRedirect(url)
# Get list of workouts available on Underarmour # Get list of workouts available on Underarmour
def get_underarmour_workout_list(user): def get_underarmour_workout_list(user):
@@ -163,14 +52,13 @@ def get_underarmour_workout_list(user):
'Content-Type': 'application/json'} 'Content-Type': 'application/json'}
url = "https://api.ua.com/v7.1/workout/?user="+str(get_userid(r.underarmourtoken))+"&order_by=-start_datetime" url = "https://api.ua.com/v7.1/workout/?user="+str(get_userid(r.underarmourtoken))+"&order_by=-start_datetime"
s = requests.get(url,headers=headers) s = requests.get(url,headers=headers)
return s return s
# Get workout summary data by Underarmour ID # Get workout summary data by Underarmour ID
def get_underarmour_workout(user,underarmourid): def get_workout(user,underarmourid):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
if (r.underarmourtoken == '') or (r.underarmourtoken is None): if (r.underarmourtoken == '') or (r.underarmourtoken is None):
return custom_exception_handler(401,s) return custom_exception_handler(401,s)
@@ -185,7 +73,14 @@ def get_underarmour_workout(user,underarmourid):
url = "https://api.ua.com/v7.1/workout/"+str(underarmourid)+"/?field_set=time_series" url = "https://api.ua.com/v7.1/workout/"+str(underarmourid)+"/?field_set=time_series"
s = requests.get(url,headers=headers) s = requests.get(url,headers=headers)
return s data = s.json()
strokedata = pd.DataFrame.from_dict({
key: pd.Series(value) for key, value in data.items()
})
return data,strokedata
# Create Workout Data for upload to Underarmour # Create Workout Data for upload to Underarmour
def createunderarmourworkoutdata(w): def createunderarmourworkoutdata(w):
@@ -319,13 +214,12 @@ def get_idfromuri(user,links):
id = links['self'][0]['id'] id = links['self'][0]['id']
typeid = links['activity_type'][0]['id'] typeid = links['activity_type'][0]['id']
typename = get_typefromid(typeid,user) typename = get_typefromid(typeid,user)
return id,typename return id,typename
def getidfromresponse(response): def getidfromresponse(response):
t = json.loads(response.text) t = response.json()
links = t["_links"] links = t["_links"]
@@ -453,3 +347,170 @@ def workout_ua_upload(user,w):
return message, uaid return message, uaid
return message, uaid return message, uaid
# Create workout from SportTracks Data, which are slightly different
# than Strava or Concept2 data
def add_workout_from_data(user,importid,data,strokedata,
source='mapmyfitness',
workoutsource='mapmyfitness'):
workouttype = 'water'
try:
comments = data['notes']
except:
comments = ''
try:
thetimezone = tz(data['start_locale_timezone'])
except:
thetimezone = 'UTC'
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['start_datetime'])
except iso8601.ParseError:
try:
rowdatetime = datetime.strptime(data['start_datetime'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
try:
rowdatetime = parser.parse(data['start_datetime'])
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
rowdatetime = datetime.strptime(data['date'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
starttimeunix = arrow.get(rowdatetime).timestamp
try:
title = data['name']
except:
title = "Imported data"
timeseries = data['time_series']
# position, distance, speed, cadence, power,
res = splituadata(timeseries['distance'])
distance = res[1]
times_distance = res[0]
try:
l = timeseries['position']
res = splituadata(l)
times_location = res[0]
latlong = res[1]
latcoord = []
loncoord = []
for coord in latlong:
lat = coord['lat']
lon = coord['lng']
latcoord.append(lat)
loncoord.append(lon)
except:
times_location = times_distance
latcoord = np.zeros(len(times_distance))
loncoord = np.zeros(len(times_distance))
if workouttype in types.otwtypes:
workouttype = 'rower'
try:
res = splituadata(timeseries['cadence'])
times_spm = res[0]
spm = res[1]
except KeyError:
times_spm = times_distance
spm = 0*times_distance
try:
res = splituadata(timeseries['heartrate'])
hr = res[1]
times_hr = res[0]
except KeyError:
times_hr = times_distance
hr = 0*times_distance
# create data series and remove duplicates
distseries = pd.Series(distance,index=times_distance)
distseries = distseries.groupby(distseries.index).first()
latseries = pd.Series(latcoord,index=times_location)
latseries = latseries.groupby(latseries.index).first()
lonseries = pd.Series(loncoord,index=times_location)
lonseries = lonseries.groupby(lonseries.index).first()
spmseries = pd.Series(spm,index=times_spm)
spmseries = spmseries.groupby(spmseries.index).first()
hrseries = pd.Series(hr,index=times_hr)
hrseries = hrseries.groupby(hrseries.index).first()
# Create dicts and big dataframe
d = {
' Horizontal (meters)': distseries,
' latitude': latseries,
' longitude': lonseries,
' Cadence (stokes/min)': spmseries,
' HRCur (bpm)' : hrseries,
}
df = pd.DataFrame(d)
df = df.groupby(level=0).last()
cum_time = df.index.values
df[' ElapsedTime (sec)'] = cum_time
velo = df[' Horizontal (meters)'].diff()/df[' ElapsedTime (sec)'].diff()
df[' Power (watts)'] = 0.0*velo
nr_rows = len(velo.values)
df[' DriveLength (meters)'] = np.zeros(nr_rows)
df[' StrokeDistance (meters)'] = np.zeros(nr_rows)
df[' DriveTime (ms)'] = np.zeros(nr_rows)
df[' StrokeRecoveryTime (ms)'] = np.zeros(nr_rows)
df[' AverageDriveForce (lbs)'] = np.zeros(nr_rows)
df[' PeakDriveForce (lbs)'] = np.zeros(nr_rows)
df[' lapIdx'] = np.zeros(nr_rows)
unixtime = cum_time+starttimeunix
unixtime[0] = starttimeunix
df['TimeStamp (sec)'] = unixtime
dt = np.diff(cum_time).mean()
wsize = round(5./dt)
df = df.fillna(0)
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
id,message = dataprep.save_workout_database(csvfilename,r,
workouttype=workouttype,
workoutsource='mapmyfitness',
title=title,
notes=comments)
return (id,message)

View File

@@ -105,9 +105,6 @@ handler403 = 'views.error403_view'
from oauth2_provider.views import base from oauth2_provider.views import base
urlpatterns = [ urlpatterns = [
# url(r'^password_change/$',auth_views.password_change),
# url(r'^password_change_done/$',auth_views.password_change_done),
# url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
url(r'^o/authorize/$', base.AuthorizationView.as_view(), name="authorize"), url(r'^o/authorize/$', base.AuthorizationView.as_view(), name="authorize"),
url(r'^o/token/$', base.TokenView.as_view(), name="token"), url(r'^o/token/$', base.TokenView.as_view(), name="token"),
url(r'^', include(router.urls)), url(r'^', include(router.urls)),
@@ -120,7 +117,6 @@ urlpatterns = [
url(r'^404/$', TemplateView.as_view(template_name='404.html'),name='404'), url(r'^404/$', TemplateView.as_view(template_name='404.html'),name='404'),
url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'), url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'),
url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'), url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'),
# url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'),
url(r'^imports/$', views.imports_view), url(r'^imports/$', views.imports_view),
url(r'^exportallworkouts/?$',views.workouts_summaries_email_view), url(r'^exportallworkouts/?$',views.workouts_summaries_email_view),
url(r'^update_empower$',views.rower_update_empower_view), url(r'^update_empower$',views.rower_update_empower_view),
@@ -321,20 +317,16 @@ urlpatterns = [
url(r'^workout/c2list/$',views.workout_c2import_view), url(r'^workout/c2list/$',views.workout_c2import_view),
url(r'^workout/c2list/(?P<page>\d+)$',views.workout_c2import_view), url(r'^workout/c2list/(?P<page>\d+)$',views.workout_c2import_view),
url(r'^workout/stravaimport/$',views.workout_stravaimport_view), url(r'^workout/stravaimport/$',views.workout_stravaimport_view),
url(r'^workout/c2import/(?P<c2id>\d+)/$',views.workout_getc2workout_view),
url(r'^workout/c2import/all/$',views.workout_getc2workout_all), url(r'^workout/c2import/all/$',views.workout_getc2workout_all),
url(r'^workout/c2import/all/(?P<page>\d+)$',views.workout_getc2workout_all), url(r'^workout/c2import/all/(?P<page>\d+)$',views.workout_getc2workout_all),
url(r'^workout/stravaimport/(?P<stravaid>\d+)/$',views.workout_getstravaworkout_view), url(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/$',views.workout_getimportview),
url(r'^workout/stravaimport/all/$',views.workout_getstravaworkout_all), url(r'^workout/stravaimport/all/$',views.workout_getstravaworkout_all),
url(r'^workout/stravaimport/next/$',views.workout_getstravaworkout_next), url(r'^workout/stravaimport/next/$',views.workout_getstravaworkout_next),
url(r'^workout/sporttracksimport/$',views.workout_sporttracksimport_view), url(r'^workout/sporttracksimport/$',views.workout_sporttracksimport_view),
url(r'^workout/sporttracksimport/(?P<sporttracksid>\d+)/$',views.workout_getsporttracksworkout_view),
url(r'^workout/sporttracksimport/all/$',views.workout_getsporttracksworkout_all), url(r'^workout/sporttracksimport/all/$',views.workout_getsporttracksworkout_all),
url(r'^workout/polarimport/$',views.workout_polarimport_view), url(r'^workout/polarimport/$',views.workout_polarimport_view),
url(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view), url(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view),
url(r'^workout/runkeeperimport/(?P<runkeeperid>\d+)/$',views.workout_getrunkeeperworkout_view),
url(r'^workout/underarmourimport/$',views.workout_underarmourimport_view), url(r'^workout/underarmourimport/$',views.workout_underarmourimport_view),
url(r'^workout/underarmourimport/(?P<underarmourid>\d+)/$',views.workout_getunderarmourworkout_view),
url(r'^workout/(?P<id>\d+)/deleteconfirm$',views.workout_delete_confirm_view), url(r'^workout/(?P<id>\d+)/deleteconfirm$',views.workout_delete_confirm_view),
url(r'^workout/(?P<id>\d+)/c2uploadw/$',views.workout_c2_upload_view), url(r'^workout/(?P<id>\d+)/c2uploadw/$',views.workout_c2_upload_view),
url(r'^workout/(?P<id>\d+)/stravauploadw/$',views.workout_strava_upload_view), url(r'^workout/(?P<id>\d+)/stravauploadw/$',views.workout_strava_upload_view),

View File

@@ -7,10 +7,14 @@ from django.conf import settings
import uuid import uuid
import datetime import datetime
import json
from django.http import HttpResponse from django.http import HttpResponse
import requests import requests
from django.http import HttpResponse
lbstoN = 4.44822 lbstoN = 4.44822
landingpages = ( landingpages = (
@@ -445,4 +449,7 @@ def get_strava_stream(r,metric,stravaid,series_type='time',fetchresolution='high
metric=metric metric=metric
) )
return requests.get(url,headers=headers)
s = requests.get(url,headers=headers)
return s

File diff suppressed because it is too large Load Diff

View File

@@ -415,3 +415,4 @@ try:
workoutemailbox = CFG['workoutemailbox'] workoutemailbox = CFG['workoutemailbox']
except KeyError: except KeyError:
workoutemailbox = 'workouts@rowsandall.com' workoutemailbox = 'workouts@rowsandall.com'