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
|
# (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
|
||||||
|
|||||||
@@ -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
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',
|
'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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
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
|
# 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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
959
rowers/views.py
959
rowers/views.py
File diff suppressed because it is too large
Load Diff
@@ -415,3 +415,4 @@ try:
|
|||||||
workoutemailbox = CFG['workoutemailbox']
|
workoutemailbox = CFG['workoutemailbox']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
workoutemailbox = 'workouts@rowsandall.com'
|
workoutemailbox = 'workouts@rowsandall.com'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user