Merge branch 'feature/revisedimports' into develop
This commit is contained in:
1
data.txt
Normal file
1
data.txt
Normal 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}
|
||||
@@ -3,42 +3,15 @@
|
||||
# (There is still some stuff defined directly in views.py. Need to
|
||||
# move that here.)
|
||||
|
||||
# Python
|
||||
import oauth2 as oauth
|
||||
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 rowers.imports import *
|
||||
import datetime
|
||||
from requests import Request, Session
|
||||
|
||||
from utils import myqueue,uniqify,isprorower, custom_exception_handler, NoTokenError
|
||||
|
||||
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
|
||||
import django_rq
|
||||
@@ -46,6 +19,18 @@ queue = django_rq.get_queue('default')
|
||||
queuelow = 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
|
||||
@@ -337,9 +322,9 @@ def createc2workoutdata_as_splits(w):
|
||||
split_data.append(thisrecord)
|
||||
|
||||
try:
|
||||
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S.%f")
|
||||
durationstr = datetime.datetime.strptime(str(w.duration),"%H:%M:%S.%f")
|
||||
except ValueError:
|
||||
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S")
|
||||
durationstr = datetime.datetime.strptime(str(w.duration),"%H:%M:%S")
|
||||
|
||||
try:
|
||||
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
|
||||
@@ -406,14 +391,19 @@ def createc2workoutdata(w):
|
||||
stroke_data.append(thisrecord)
|
||||
|
||||
try:
|
||||
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S.%f")
|
||||
durationstr = datetime.datetime.strptime(str(w.duration),"%H:%M:%S.%f")
|
||||
except ValueError:
|
||||
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S")
|
||||
durationstr = datetime.datetime.strptime(str(w.duration),"%H:%M:%S")
|
||||
|
||||
workouttype = w.workouttype
|
||||
if workouttype in otwtypes:
|
||||
workouttype = 'water'
|
||||
|
||||
try:
|
||||
startdatetime = w.startdatetime.isoformat()
|
||||
except AttributeError:
|
||||
startdate = datetime.datetime.combine(w.date,datetime.time())
|
||||
|
||||
data = {
|
||||
"type": workouttype,
|
||||
"date": w.startdatetime.isoformat(),
|
||||
@@ -451,6 +441,7 @@ def do_refresh_token(refreshtoken):
|
||||
prepped.body+=scope
|
||||
|
||||
response = s.send(prepped)
|
||||
|
||||
token_json = response.json()
|
||||
try:
|
||||
thetoken = token_json['access_token']
|
||||
@@ -488,19 +479,8 @@ def get_token(code):
|
||||
prepped.body+="&scope="
|
||||
prepped.body+=scope
|
||||
|
||||
print prepped.body
|
||||
|
||||
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()
|
||||
|
||||
try:
|
||||
@@ -540,14 +520,14 @@ def make_authorization_url(request):
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Get workout from C2 ID
|
||||
def get_c2_workout(user,c2id):
|
||||
def get_workout(user,c2id):
|
||||
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)
|
||||
return custom_exception_handler(401,s) ,0
|
||||
elif (timezone.now()>r.tokenexpirydate):
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401,s)
|
||||
return custom_exception_handler(401,s),0
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
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)
|
||||
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
|
||||
def get_c2_workout_strokes(user,c2id):
|
||||
@@ -689,7 +690,8 @@ def workout_c2_upload(user,w):
|
||||
w.save()
|
||||
elif (response.status_code == 201 or response.status_code == 200):
|
||||
try:
|
||||
s= json.loads(response.text)
|
||||
# s= json.loads(response.text)
|
||||
s = response.json()
|
||||
c2id = s['data']['id']
|
||||
w.uploadedtoc2 = c2id
|
||||
w.save()
|
||||
@@ -699,7 +701,6 @@ def workout_c2_upload(user,w):
|
||||
c2id = 0
|
||||
|
||||
else:
|
||||
print response.status_code
|
||||
message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text
|
||||
c2id = 0
|
||||
|
||||
@@ -713,7 +714,7 @@ def rower_c2_token_refresh(user):
|
||||
access_token = res[0]
|
||||
expires_in = res[1]
|
||||
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.c2token = access_token
|
||||
@@ -725,3 +726,154 @@ def rower_c2_token_refresh(user):
|
||||
else:
|
||||
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
|
||||
|
||||
@@ -34,7 +34,7 @@ from rowingdata import (
|
||||
MysteryParser, BoatCoachOTWParser,QuiskeParser,
|
||||
painsledDesktopParser, speedcoachParser, ErgStickParser,
|
||||
SpeedCoach2Parser, FITParser, fitsummarydata,
|
||||
RitmoTimeParser,
|
||||
RitmoTimeParser,KinoMapParser,
|
||||
make_cumvalues,cumcpdata,ExcelTemplate,
|
||||
summarydata, get_file_type,
|
||||
)
|
||||
@@ -1073,6 +1073,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
|
||||
return (w.id, message)
|
||||
|
||||
parsers = {
|
||||
'kinomap': KinoMapParser,
|
||||
'xls': ExcelTemplate,
|
||||
'rp': RowProParser,
|
||||
'tcx':TCXParser,
|
||||
@@ -1173,6 +1174,7 @@ def new_workout_from_file(r, f2,
|
||||
message = None
|
||||
try:
|
||||
fileformat = get_file_type(f2)
|
||||
print fileformat,'aap'
|
||||
except IOError:
|
||||
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."
|
||||
|
||||
244
rowers/imports.py
Normal file
244
rowers/imports.py
Normal 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
|
||||
|
||||
@@ -1836,10 +1836,12 @@ class RowerExportForm(ModelForm):
|
||||
'stravaexportas',
|
||||
'polar_auto_import',
|
||||
'c2_auto_export',
|
||||
'c2_auto_import',
|
||||
'mapmyfitness_auto_export',
|
||||
'runkeeper_auto_export',
|
||||
'sporttracks_auto_export',
|
||||
'strava_auto_export',
|
||||
'strava_auto_import',
|
||||
'trainingpeaks_auto_export',
|
||||
]
|
||||
|
||||
|
||||
@@ -1,34 +1,6 @@
|
||||
# All the functionality needed to connect to Runkeeper
|
||||
|
||||
# Python
|
||||
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 rowers.imports import *
|
||||
import re
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
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,
|
||||
)
|
||||
|
||||
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
|
||||
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 NoTokenError("User has no token")
|
||||
else:
|
||||
thetoken = r.runkeepertoken
|
||||
|
||||
return thetoken
|
||||
return imports_open(user,oauth_data)
|
||||
|
||||
# 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
|
||||
return imports_get_token(code,oauth_data)
|
||||
|
||||
# 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)
|
||||
return imports_make_authorization_url(oauth_data)
|
||||
|
||||
# Get list of workouts available on Runkeeper
|
||||
def get_runkeeper_workout_list(user):
|
||||
@@ -105,7 +71,7 @@ def get_runkeeper_workout_list(user):
|
||||
return s
|
||||
|
||||
# Get workout summary data by Runkeeper ID
|
||||
def get_runkeeper_workout(user,runkeeperid):
|
||||
def get_workout(user,runkeeperid):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.runkeepertoken == '') or (r.runkeepertoken is None):
|
||||
return custom_exception_handler(401,s)
|
||||
@@ -119,7 +85,16 @@ def get_runkeeper_workout(user,runkeeperid):
|
||||
url = "https://api.runkeeper.com/fitnessActivities/"+str(runkeeperid)
|
||||
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
|
||||
def createrunkeeperworkoutdata(w):
|
||||
@@ -227,7 +202,9 @@ def createrunkeeperworkoutdata(w):
|
||||
# upload
|
||||
def getidfromresponse(response):
|
||||
uri = response.headers["Location"]
|
||||
id = uri[len(uri)-9:]
|
||||
|
||||
tester = re.compile('^\/fitnessActivities\/(\d+)$')
|
||||
id = int(tester.match(uri).group(1))
|
||||
|
||||
return int(id)
|
||||
|
||||
@@ -328,3 +305,183 @@ def workout_runkeeper_upload(user,w):
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,144 +1,42 @@
|
||||
# All the functionality to connect to SportTracks
|
||||
|
||||
# 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 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
|
||||
from rowers.imports import *
|
||||
import re
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
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 NoTokenError("User has no token")
|
||||
else:
|
||||
if (timezone.now()>r.sporttrackstokenexpirydate):
|
||||
thetoken = rower_sporttracks_token_refresh(user)
|
||||
else:
|
||||
thetoken = r.sporttrackstoken
|
||||
|
||||
return thetoken
|
||||
return imports_open(user, oauth_data)
|
||||
|
||||
|
||||
# Refresh ST token using refresh token
|
||||
def do_refresh_token(refreshtoken):
|
||||
client_auth = requests.auth.HTTPBasicAuth(SPORTTRACKS_CLIENT_ID, SPORTTRACKS_CLIENT_SECRET)
|
||||
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]
|
||||
return imports_do_refresh_token(refreshtoken, oauth_data)
|
||||
|
||||
# Exchange ST access code for long-lived ST access token
|
||||
def get_token(code):
|
||||
client_auth = requests.auth.HTTPBasicAuth(SPORTTRACKS_CLIENT_ID, SPORTTRACKS_CLIENT_SECRET)
|
||||
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]
|
||||
return imports_get_token(code,oauth_data)
|
||||
|
||||
# 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": 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)
|
||||
return imports_make_authorization_url(oauth_data)
|
||||
|
||||
# This is token refresh. Looks for tokens in our database, then refreshes
|
||||
def rower_sporttracks_token_refresh(user):
|
||||
@@ -155,6 +53,7 @@ def rower_sporttracks_token_refresh(user):
|
||||
r.sporttracksrefreshtoken = refresh_token
|
||||
|
||||
r.save()
|
||||
|
||||
return r.sporttrackstoken
|
||||
|
||||
# Get list of workouts available on SportTracks
|
||||
@@ -178,7 +77,7 @@ def get_sporttracks_workout_list(user):
|
||||
return s
|
||||
|
||||
# Get workout summary data by SportTracks ID
|
||||
def get_sporttracks_workout(user,sporttracksid):
|
||||
def get_workout(user,sporttracksid):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
|
||||
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)
|
||||
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
|
||||
def createsporttracksworkoutdata(w):
|
||||
@@ -223,9 +128,9 @@ def createsporttracksworkoutdata(w):
|
||||
d[0] = d[1]
|
||||
t = t.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]
|
||||
hr = row.df[' HRCur (bpm)'].astype(int)
|
||||
hr = row.df[' HRCur (bpm)'].astype(int).values
|
||||
|
||||
haslatlon=1
|
||||
|
||||
@@ -240,7 +145,7 @@ def createsporttracksworkoutdata(w):
|
||||
|
||||
haspower = 1
|
||||
try:
|
||||
power = row.df[' Power (watts)'].values
|
||||
power = row.df[' Power (watts)'].astype(int).values
|
||||
except KeyError:
|
||||
haspower = 0
|
||||
|
||||
@@ -311,9 +216,12 @@ def createsporttracksworkoutdata(w):
|
||||
# Obtain SportTracks Workout ID from the response returned on successful
|
||||
# upload
|
||||
def getidfromresponse(response):
|
||||
t = json.loads(response.text)
|
||||
t = response.json()
|
||||
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)
|
||||
|
||||
@@ -324,7 +232,8 @@ def workout_sporttracks_upload(user,w):
|
||||
# ready to upload. Hurray
|
||||
r = w.user
|
||||
|
||||
thetoken = sporttracks_open(user)
|
||||
res = sporttracks_open(user)
|
||||
thetoken = res[0]
|
||||
|
||||
if (checkworkoutuser(user,w)):
|
||||
data = createsporttracksworkoutdata(w)
|
||||
@@ -349,7 +258,7 @@ def workout_sporttracks_upload(user,w):
|
||||
w.save()
|
||||
return message, stid
|
||||
elif (response.status_code == 201 or response.status_code==200):
|
||||
s= json.loads(response.text)
|
||||
s= response.json()
|
||||
stid = getidfromresponse(response)
|
||||
w.uploadedtosporttracks = stid
|
||||
w.save()
|
||||
@@ -366,3 +275,176 @@ def workout_sporttracks_upload(user,w):
|
||||
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)
|
||||
|
||||
@@ -1,33 +1,7 @@
|
||||
# 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.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
|
||||
|
||||
@@ -36,76 +10,49 @@ queue = django_rq.get_queue('default')
|
||||
queuelow = 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 utils import uniqify,isprorower,myqueue,NoTokenError, custom_exception_handler
|
||||
from uuid import uuid4
|
||||
|
||||
import stravalib
|
||||
from stravalib.exc import ActivityUploadFailed,TimeoutExceeded
|
||||
import iso8601
|
||||
|
||||
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:
|
||||
from json.decoder import JSONDecodeError
|
||||
except ImportError:
|
||||
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
|
||||
def get_token(code):
|
||||
client_auth = requests.auth.HTTPBasicAuth(STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET)
|
||||
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]
|
||||
return imports_get_token(code, oauth_data)
|
||||
|
||||
# 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
|
||||
|
||||
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)
|
||||
return imports_make_authorization_url(oauth_data)
|
||||
|
||||
# Get list of workouts available on Strava
|
||||
def get_strava_workout_list(user,limit_n=0):
|
||||
@@ -244,8 +191,10 @@ def create_async_workout(alldata,user,stravaid,debug=False):
|
||||
|
||||
from utils import get_strava_stream
|
||||
|
||||
|
||||
|
||||
# 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)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None):
|
||||
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)
|
||||
latlongjson = get_strava_stream(r,'latlng',stravaid)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
t = np.array(timejson.json()[0]['data'])
|
||||
nr_rows = len(t)
|
||||
d = np.array(distancejson.json()[1]['data'])
|
||||
if nr_rows == 0:
|
||||
return (0,"Error: Time data had zero length")
|
||||
|
||||
except IndexError:
|
||||
d = 0*t
|
||||
# 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[np.isinf(strokelength)] = 0.0
|
||||
|
||||
|
||||
pace = 500./(1.0*velo2)
|
||||
pace[np.isinf(pace)] = 0.0
|
||||
|
||||
@@ -337,6 +290,7 @@ def get_strava_workout(user,stravaid):
|
||||
'strokelength':strokelength,
|
||||
})
|
||||
|
||||
|
||||
# startdatetime = datetime.datetime.strptime(startdatetime,"%Y-%m-%d-%H:%M:%S")
|
||||
|
||||
return [workoutsummary,df]
|
||||
@@ -421,6 +375,144 @@ def handle_stravaexport(f2,workoutname,stravatoken,description='',
|
||||
|
||||
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):
|
||||
message = "Uploading to Strava"
|
||||
@@ -436,9 +528,10 @@ def workout_strava_upload(user,w):
|
||||
tcxfile,tcxmesg = 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')
|
||||
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
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
1
rowers/testdata/c2jsonstrokedata.txt
vendored
Normal file
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
1
rowers/testdata/c2jsonworkoutdata.txt
vendored
Normal 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
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
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
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
1
rowers/testdata/rkworkoutslist.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
1
rowers/testdata/sporttracksstrokedata.txt
vendored
Normal file
1
rowers/testdata/sporttracksstrokedata.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
1
rowers/testdata/sporttracksworkouts.txt
vendored
Normal file
1
rowers/testdata/sporttracksworkouts.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
2
rowers/testdata/stravadistancetestdata.txt
vendored
2
rowers/testdata/stravadistancetestdata.txt
vendored
File diff suppressed because one or more lines are too long
2
rowers/testdata/stravahrtestdata.txt
vendored
2
rowers/testdata/stravahrtestdata.txt
vendored
File diff suppressed because one or more lines are too long
2
rowers/testdata/stravalatlongtestdata.txt
vendored
2
rowers/testdata/stravalatlongtestdata.txt
vendored
File diff suppressed because one or more lines are too long
2
rowers/testdata/stravaspmtestdata.txt
vendored
2
rowers/testdata/stravaspmtestdata.txt
vendored
File diff suppressed because one or more lines are too long
2
rowers/testdata/stravatimetestdata.txt
vendored
2
rowers/testdata/stravatimetestdata.txt
vendored
File diff suppressed because one or more lines are too long
2
rowers/testdata/stravavelotestdata.txt
vendored
2
rowers/testdata/stravavelotestdata.txt
vendored
File diff suppressed because one or more lines are too long
1
rowers/testdata/stravaworkoutlist.txt
vendored
Normal file
1
rowers/testdata/stravaworkoutlist.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
1
rowers/testdata/stravaworkoutsummary.txt
vendored
Normal file
1
rowers/testdata/stravaworkoutsummary.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
1
rowers/testdata/tpuploadresponse.txt
vendored
Normal file
1
rowers/testdata/tpuploadresponse.txt
vendored
Normal 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}]
|
||||
1
rowers/testdata/uapostworkoutresponse.txt
vendored
Normal file
1
rowers/testdata/uapostworkoutresponse.txt
vendored
Normal 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
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
1
rowers/testdata/uauser.txt
vendored
Normal 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
1
rowers/testdata/uaworkoutlist.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
981
rowers/tests.py
981
rowers/tests.py
File diff suppressed because it is too large
Load Diff
@@ -1,40 +1,12 @@
|
||||
# All the functionality needed to connect to Runkeeper
|
||||
from rowers.imports import *
|
||||
|
||||
# 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
|
||||
from math import sin,cos,atan2,sqrt
|
||||
import os,sys
|
||||
import urllib
|
||||
|
||||
import base64
|
||||
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 (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
@@ -50,116 +22,35 @@ from django_rq import job
|
||||
import time
|
||||
from async_messages import message_user,messages
|
||||
|
||||
|
||||
|
||||
|
||||
from utils import geo_distance, NoTokenError,ewmovingaverage, custom_exception_handler
|
||||
|
||||
oauth_data = {
|
||||
'client_id': TP_CLIENT_ID,
|
||||
'client_secret': TP_CLIENT_SECRET,
|
||||
'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
|
||||
def tp_open(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
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
|
||||
return imports_open(user, oauth_data)
|
||||
|
||||
# Refresh ST token using refresh token
|
||||
def do_refresh_token(refreshtoken):
|
||||
client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET)
|
||||
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]
|
||||
return imports_do_refresh_token(refreshtoken, oauth_data)
|
||||
|
||||
# Exchange access code for long-lived access token
|
||||
def get_token(code):
|
||||
client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET)
|
||||
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
|
||||
return imports_get_token(code, oauth_data)
|
||||
|
||||
# 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": 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)
|
||||
|
||||
return imports_make_authorization_url(oauth_data)
|
||||
|
||||
|
||||
def getidfromresponse(response):
|
||||
@@ -229,7 +120,6 @@ def uploadactivity(access_token,filename,description='',
|
||||
data = json.dumps(data),
|
||||
headers=headers)
|
||||
|
||||
print resp.status_code
|
||||
if resp.status_code != 200:
|
||||
if settings.DEBUG:
|
||||
print resp.status_code
|
||||
@@ -237,24 +127,11 @@ def uploadactivity(access_token,filename,description='',
|
||||
print ""
|
||||
print headers
|
||||
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
|
||||
else:
|
||||
return resp.json()[0]["Id"],"ok",200,""
|
||||
|
||||
return 0
|
||||
return 0,0,0,0
|
||||
|
||||
|
||||
def workout_tp_upload(user,w):
|
||||
|
||||
@@ -1,152 +1,41 @@
|
||||
# All the functionality needed to connect to Runkeeper
|
||||
|
||||
# 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 rowers.imports import *
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_CLIENT_SECRET,
|
||||
UNDERARMOUR_REDIRECT_URI,UNDERARMOUR_CLIENT_KEY,
|
||||
UNDERARMOUR_CLIENT_KEY,
|
||||
UNDERARMOUR_CLIENT_SECRET,
|
||||
UNDERARMOUR_REDIRECT_URI,
|
||||
)
|
||||
|
||||
from utils import NoTokenError,ewmovingaverage
|
||||
|
||||
from utils import geo_distance, custom_exception_handler
|
||||
|
||||
|
||||
oauth_data = {
|
||||
'client_id': UNDERARMOUR_CLIENT_KEY,
|
||||
'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
|
||||
def underarmour_open(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
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
|
||||
return imports_open(user,oauth_data)
|
||||
|
||||
# Refresh ST token using refresh token
|
||||
def do_refresh_token(refreshtoken,access_token):
|
||||
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET)
|
||||
post_data = {"grant_type": "refresh_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]
|
||||
return imports_do_refresh_token(
|
||||
refreshtoken,oauth_data,access_token=access_token
|
||||
)
|
||||
|
||||
# Exchange access code for long-lived access token
|
||||
def get_token(code):
|
||||
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET)
|
||||
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
|
||||
return imports_get_token(code,oauth_data)
|
||||
|
||||
# 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": 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)
|
||||
return imports_make_authorization_url(oauth_data)
|
||||
|
||||
# Get list of workouts available on Underarmour
|
||||
def get_underarmour_workout_list(user):
|
||||
@@ -163,14 +52,13 @@ def get_underarmour_workout_list(user):
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/workout/?user="+str(get_userid(r.underarmourtoken))+"&order_by=-start_datetime"
|
||||
|
||||
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
|
||||
return s
|
||||
|
||||
# Get workout summary data by Underarmour ID
|
||||
def get_underarmour_workout(user,underarmourid):
|
||||
def get_workout(user,underarmourid):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||
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"
|
||||
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
|
||||
def createunderarmourworkoutdata(w):
|
||||
@@ -319,13 +214,12 @@ def get_idfromuri(user,links):
|
||||
id = links['self'][0]['id']
|
||||
typeid = links['activity_type'][0]['id']
|
||||
|
||||
|
||||
typename = get_typefromid(typeid,user)
|
||||
|
||||
return id,typename
|
||||
|
||||
def getidfromresponse(response):
|
||||
t = json.loads(response.text)
|
||||
t = response.json()
|
||||
|
||||
links = t["_links"]
|
||||
|
||||
@@ -453,3 +347,170 @@ def workout_ua_upload(user,w):
|
||||
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)
|
||||
|
||||
|
||||
@@ -105,9 +105,6 @@ handler403 = 'views.error403_view'
|
||||
from oauth2_provider.views import base
|
||||
|
||||
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/token/$', base.TokenView.as_view(), name="token"),
|
||||
url(r'^', include(router.urls)),
|
||||
@@ -120,7 +117,6 @@ urlpatterns = [
|
||||
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'^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'^exportallworkouts/?$',views.workouts_summaries_email_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/(?P<page>\d+)$',views.workout_c2import_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/(?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/next/$',views.workout_getstravaworkout_next),
|
||||
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/polarimport/$',views.workout_polarimport_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/(?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+)/c2uploadw/$',views.workout_c2_upload_view),
|
||||
url(r'^workout/(?P<id>\d+)/stravauploadw/$',views.workout_strava_upload_view),
|
||||
|
||||
@@ -7,10 +7,14 @@ from django.conf import settings
|
||||
import uuid
|
||||
import datetime
|
||||
|
||||
import json
|
||||
|
||||
from django.http import HttpResponse
|
||||
|
||||
import requests
|
||||
|
||||
from django.http import HttpResponse
|
||||
|
||||
lbstoN = 4.44822
|
||||
|
||||
landingpages = (
|
||||
@@ -445,4 +449,7 @@ def get_strava_stream(r,metric,stravaid,series_type='time',fetchresolution='high
|
||||
metric=metric
|
||||
)
|
||||
|
||||
return requests.get(url,headers=headers)
|
||||
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
return s
|
||||
|
||||
949
rowers/views.py
949
rowers/views.py
File diff suppressed because it is too large
Load Diff
@@ -415,3 +415,4 @@ try:
|
||||
workoutemailbox = CFG['workoutemailbox']
|
||||
except KeyError:
|
||||
workoutemailbox = 'workouts@rowsandall.com'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user