Merge branch 'feature/newc2' into develop
This commit is contained in:
@@ -46,9 +46,6 @@ class RowerInline(admin.StackedInline):
|
||||
'sporttrackstoken','sporttrackstokenexpirydate',
|
||||
'sporttracksrefreshtoken',
|
||||
'sporttracks_auto_export',
|
||||
'underarmourtoken','underarmourtokenexpirydate',
|
||||
'underarmourrefreshtoken',
|
||||
'mapmyfitness_auto_export',
|
||||
'tptoken','tptokenexpirydate','tprefreshtoken',
|
||||
'trainingpeaks_auto_export',
|
||||
'polartoken','polartokenexpirydate',
|
||||
@@ -57,7 +54,6 @@ class RowerInline(admin.StackedInline):
|
||||
'stravatoken','stravatokenexpirydate','stravarefreshtoken',
|
||||
'stravaexportas','strava_auto_export',
|
||||
'strava_auto_import',
|
||||
'runkeepertoken','runkeeper_auto_export',
|
||||
'garmintoken','garminrefreshtoken')}),
|
||||
('Team',
|
||||
{'fields':('friends','privacy','team')}),
|
||||
|
||||
+18
-281
@@ -22,15 +22,7 @@ from scipy import optimize
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
from pytz.exceptions import UnknownTimeZoneError
|
||||
|
||||
def dologging(s):
|
||||
tstamp = time.localtime()
|
||||
timestamp = time.strftime('%b-%d-%Y %H:%M:%S', tstamp)
|
||||
with open('debuglog.log','a') as f:
|
||||
f.write('\n')
|
||||
f.write(timestamp)
|
||||
f.write(' ')
|
||||
f.write(s)
|
||||
from rowers.utils import dologging
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
@@ -39,6 +31,7 @@ from rowsandall_app.settings import (
|
||||
|
||||
from rowers.tasks import (
|
||||
handle_c2_import_stroke_data, handle_c2_sync, handle_c2_async_workout,
|
||||
handle_c2_getworkout
|
||||
)
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
@@ -832,92 +825,19 @@ def make_authorization_url(request): # pragma: no cover
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Get workout from C2 ID
|
||||
def get_workout(user,c2id,do_async=False):
|
||||
def get_workout(user,c2id,do_async=True):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.c2token == '') or (r.c2token is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s) ,0
|
||||
elif (timezone.now()>r.tokenexpirydate):
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401,s),0
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.c2token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://log.concept2.com/api/users/me/results/"+str(c2id)
|
||||
s = requests.get(url,headers=headers)
|
||||
thetoken = c2_open(user)
|
||||
|
||||
if s.status_code != 200: # pragma: no cover
|
||||
if s.status_code == 404:
|
||||
raise PermissionDenied("You have no access to this resource")
|
||||
else:
|
||||
s = "Something went wrong with the import"
|
||||
return custom_exception_handler(401,s), 0
|
||||
job = myqueue(queuehigh,
|
||||
handle_c2_getworkout,
|
||||
user.id,
|
||||
r.c2token,
|
||||
c2id,
|
||||
r.defaulttimezone)
|
||||
|
||||
data = s.json()['data']
|
||||
alldata = {c2id:data}
|
||||
splitdata = None
|
||||
#with open('c2temp.json','w') as f:
|
||||
# f.write(json.dumps(s.json()))
|
||||
# print(s.json())
|
||||
return 1
|
||||
|
||||
if do_async:
|
||||
print('aap',alldata)
|
||||
job = myqueue(queuehigh,
|
||||
handle_c2_async_workout,
|
||||
alldata,
|
||||
r.user.id,
|
||||
r.c2token,
|
||||
c2id,
|
||||
0,
|
||||
r.defaulttimezone)
|
||||
|
||||
return data, pd.DataFrame()
|
||||
|
||||
|
||||
if 'workout' in data:
|
||||
if 'splits' in data['workout']: # pragma: no cover
|
||||
splitdata = data['workout']['splits']
|
||||
elif 'intervals' in data['workout']: # pragma: no cover
|
||||
splitdata = data['workout']['intervals']
|
||||
else: # pragma: no cover
|
||||
splitdata = None
|
||||
|
||||
# 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: # pragma: no cover
|
||||
strokedata = pd.DataFrame()
|
||||
else: # pragma: no cover
|
||||
strokedata = pd.DataFrame()
|
||||
|
||||
|
||||
return data,strokedata
|
||||
|
||||
# Get stroke data belonging to C2 ID
|
||||
def get_c2_workout_strokes(user,c2id):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.c2token == '') or (r.c2token is None): # pragma: no cover
|
||||
return custom_exception_handler(401,s)
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
elif (timezone.now()>r.tokenexpirydate): # pragma: no cover
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401,s)
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.c2token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://log.concept2.com/api/users/me/results/"+str(c2id)+"/strokes"
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
return s
|
||||
|
||||
# Get list of C2 workouts. We load only the first page,
|
||||
# assuming that users don't want to import their old workouts
|
||||
@@ -1011,6 +931,7 @@ def default(o): # pragma: no cover
|
||||
# Uploading workout
|
||||
def workout_c2_upload(user,w,asynchron=False):
|
||||
message = 'trying C2 upload'
|
||||
|
||||
try:
|
||||
if mytypes.c2mapping[w.workouttype] is None: # pragma: no cover
|
||||
return "This workout type cannot be uploaded to Concept2",0
|
||||
@@ -1022,14 +943,16 @@ def workout_c2_upload(user,w,asynchron=False):
|
||||
r = Rower.objects.get(user=user)
|
||||
|
||||
# ready to upload. Hurray
|
||||
|
||||
if (is_workout_user(user,w)):
|
||||
|
||||
c2userid = get_userid(r.c2token)
|
||||
if not c2userid: # pragma: no cover
|
||||
raise NoTokenError("User has no token")
|
||||
|
||||
dologging('Upload to C2 user {userid}'.format(userid=user.id))
|
||||
dologging('debuglog.log','Upload to C2 user {userid}'.format(userid=user.id))
|
||||
data = createc2workoutdata(w)
|
||||
dologging(json.dumps(data))
|
||||
dologging('debuglog.log',json.dumps(data))
|
||||
|
||||
if data == 0: # pragma: no cover
|
||||
return "Error: No data file. Contact info@rowsandall.com if the problem persists",0
|
||||
@@ -1040,7 +963,7 @@ def workout_c2_upload(user,w,asynchron=False):
|
||||
'Content-Type': 'application/json'}
|
||||
import urllib
|
||||
url = "https://log.concept2.com/api/users/%s/results" % (c2userid)
|
||||
if not asynchron:
|
||||
if not asynchron: # pragma: no cover
|
||||
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
|
||||
|
||||
|
||||
@@ -1060,6 +983,7 @@ def workout_c2_upload(user,w,asynchron=False):
|
||||
message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text
|
||||
c2id = 0
|
||||
else: # pragma: no cover
|
||||
|
||||
job = myqueue(queue,
|
||||
handle_c2_sync,
|
||||
w.id,
|
||||
@@ -1089,190 +1013,3 @@ def rower_c2_token_refresh(user):
|
||||
return r.c2token
|
||||
else: # pragma: no cover
|
||||
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 = mytypes.c2mappinginv[data['type']]
|
||||
except KeyError: # pragma: no cover
|
||||
workouttype = 'rower'
|
||||
|
||||
if workouttype not in [x[0] for x in Workout.workouttypes]: # pragma: no cover
|
||||
workouttype = 'other'
|
||||
try:
|
||||
comments = data['comments']
|
||||
except: # pragma: no cover
|
||||
comments = ' '
|
||||
|
||||
try:
|
||||
thetimezone = pytz.timezone(data['timezone'])
|
||||
except UnknownTimeZoneError:
|
||||
thetimezone = 'UTC'
|
||||
|
||||
|
||||
r = Rower.objects.get(user=user)
|
||||
|
||||
try:
|
||||
rowdatetime = iso8601.parse_date(data['date_utc'])
|
||||
thetimezone = 'UTC'
|
||||
except KeyError: # pragma: no cover
|
||||
rowdatetime = iso8601.parse_date(data['start_date'])
|
||||
rowdatetime = rowdatetime.make_aware(thetimezone)
|
||||
except ParseError: # pragma: no cover
|
||||
rowdatetime = iso8601.parse_date(data['date'])
|
||||
rowdatetime = rowdatetime.make_aware(thetimezone)
|
||||
|
||||
|
||||
try:
|
||||
c2intervaltype = data['workout_type']
|
||||
|
||||
except KeyError: # pragma: no cover
|
||||
c2intervaltype = ''
|
||||
|
||||
try:
|
||||
title = data['name']
|
||||
except KeyError:
|
||||
title = ""
|
||||
try:
|
||||
t = data['comments'].split('\n', 1)[0]
|
||||
title += t[:40]
|
||||
except: # pragma: no cover
|
||||
title = ''
|
||||
|
||||
try:
|
||||
comments = data['comments']
|
||||
except KeyError: # pragma: no cover
|
||||
comments = ''
|
||||
|
||||
starttimeunix = arrow.get(rowdatetime).timestamp()
|
||||
|
||||
res = make_cumvalues(0.1*strokedata['t'])
|
||||
cum_time = res[0]
|
||||
lapidx = res[1]
|
||||
|
||||
totaltime = data['time']/10.
|
||||
starttimeunix = starttimeunix - totaltime
|
||||
|
||||
|
||||
unixtime = cum_time+starttimeunix
|
||||
# unixtime[0] = starttimeunix
|
||||
seconds = 0.1*strokedata.loc[:,'t']
|
||||
|
||||
nr_rows = len(unixtime)
|
||||
|
||||
try: # pragma: no cover
|
||||
latcoord = strokedata.loc[:,'lat']
|
||||
loncoord = strokedata.loc[:,'lon']
|
||||
except:
|
||||
latcoord = np.zeros(nr_rows)
|
||||
loncoord = np.zeros(nr_rows)
|
||||
|
||||
|
||||
try:
|
||||
strokelength = strokedata.loc[:,'strokelength']
|
||||
except:
|
||||
strokelength = np.zeros(nr_rows)
|
||||
|
||||
dist2 = 0.1*strokedata.loc[:,'d']
|
||||
|
||||
try:
|
||||
spm = strokedata.loc[:,'spm']
|
||||
except KeyError: # pragma: no cover
|
||||
spm = 0*dist2
|
||||
|
||||
try:
|
||||
hr = strokedata.loc[:,'hr']
|
||||
except KeyError: # pragma: no cover
|
||||
hr = 0*spm
|
||||
pace = strokedata.loc[:,'p']/10.
|
||||
pace = np.clip(pace,0,1e4)
|
||||
pace = pace.replace(0,300)
|
||||
|
||||
velo = 500./pace
|
||||
power = 2.8*velo**3
|
||||
if workouttype in ['bike','bikeerg']: # pragma: no cover
|
||||
velo = 1000./pace
|
||||
pace = 500./velo
|
||||
|
||||
# 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,
|
||||
' WorkoutState': 4,
|
||||
' 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: # pragma: no cover
|
||||
totaldist = 0
|
||||
totaltime = 0
|
||||
else: # pragma: no cover
|
||||
totaldist = 0
|
||||
totaltime = 0
|
||||
|
||||
id,message = dataprep.save_workout_database(
|
||||
csvfilename,r,
|
||||
workouttype=workouttype,
|
||||
title=title,notes=comments,
|
||||
workoutsource=workoutsource,
|
||||
dosummary=True,dosmooth=False,
|
||||
)
|
||||
|
||||
|
||||
w = Workout.objects.get(id=id)
|
||||
|
||||
try:
|
||||
local_tz = pytz.timezone(data['timezone'])
|
||||
except UnknownTimeZoneError:
|
||||
local_tz = pytz.utc
|
||||
# local_tz = pytz.timezone(thetimezone)
|
||||
|
||||
w.startdatetime = w.startdatetime.astimezone(local_tz)
|
||||
w.starttime = w.startdatetime.strftime('%H:%M:%S')
|
||||
w.timezone = local_tz
|
||||
|
||||
w.duration = dataprep.totaltime_sec_to_string(totaltime)
|
||||
w.distance = totaldist
|
||||
|
||||
|
||||
w.save()
|
||||
|
||||
|
||||
return id,message
|
||||
|
||||
+1
-1
@@ -800,7 +800,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True,
|
||||
|
||||
def getpartofday(row,r):
|
||||
workoutstartdatetime = row.rowdatetime
|
||||
try:
|
||||
try: # pragma: no cover
|
||||
latavg = row.df[' latitude'].mean()
|
||||
lonavg = row.df[' longitude'].mean()
|
||||
|
||||
|
||||
+3
-11
@@ -71,26 +71,18 @@ def splitstdata(lijst):
|
||||
|
||||
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 (TypeError,AttributeError,KeyError):
|
||||
except (TypeError,AttributeError,KeyError): # pragma: no cover
|
||||
refreshtoken = None
|
||||
|
||||
try:
|
||||
tokenexpirydate = getattr(r,oauth_data['expirydatename'])
|
||||
except (TypeError,AttributeError,KeyError):
|
||||
except (TypeError,AttributeError,KeyError): # pragma: no cover
|
||||
tokenexpirydate = None
|
||||
|
||||
|
||||
@@ -243,7 +235,7 @@ def imports_get_token(
|
||||
base_uri,
|
||||
data=json.dumps(post_data),
|
||||
headers=headers)
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
response = requests.post(
|
||||
base_uri,
|
||||
data=post_data,
|
||||
|
||||
@@ -988,11 +988,6 @@ class Rower(models.Model):
|
||||
sporttracksrefreshtoken = models.CharField(default='',max_length=200,
|
||||
blank=True,null=True)
|
||||
sporttracks_auto_export = models.BooleanField(default=False)
|
||||
underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
||||
underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True)
|
||||
underarmourrefreshtoken = models.CharField(default='',max_length=200,
|
||||
blank=True,null=True)
|
||||
mapmyfitness_auto_export = models.BooleanField(default=False)
|
||||
tptoken = models.CharField(default='',max_length=1000,blank=True,null=True)
|
||||
tptokenexpirydate = models.DateTimeField(blank=True,null=True)
|
||||
tprefreshtoken = models.CharField(default='',max_length=1000,
|
||||
@@ -1041,9 +1036,6 @@ class Rower(models.Model):
|
||||
strava_auto_export = models.BooleanField(default=False)
|
||||
strava_auto_import = models.BooleanField(default=False)
|
||||
strava_auto_delete = models.BooleanField(default=False)
|
||||
runkeepertoken = models.CharField(default='',max_length=200,
|
||||
blank=True,null=True)
|
||||
runkeeper_auto_export = models.BooleanField(default=False)
|
||||
|
||||
|
||||
privacychoices = (
|
||||
@@ -3201,9 +3193,7 @@ class Workout(models.Model):
|
||||
maxhr = models.BigIntegerField(blank=True,null=True)
|
||||
uploadedtostrava = models.BigIntegerField(default=0)
|
||||
uploadedtosporttracks = models.BigIntegerField(default=0)
|
||||
uploadedtounderarmour = models.BigIntegerField(default=0)
|
||||
uploadedtotp = models.BigIntegerField(default=0)
|
||||
uploadedtorunkeeper = models.BigIntegerField(default=0)
|
||||
uploadedtogarmin = models.BigIntegerField(default=0)
|
||||
uploadedtorp3 = models.BigIntegerField(default=0)
|
||||
uploadedtonk = models.BigIntegerField(default=0)
|
||||
@@ -3283,9 +3273,7 @@ class TombStone(models.Model):
|
||||
uploadedtoc2 = models.IntegerField(default=0)
|
||||
uploadedtostrava = models.BigIntegerField(default=0)
|
||||
uploadedtosporttracks = models.BigIntegerField(default=0)
|
||||
uploadedtounderarmour = models.BigIntegerField(default=0)
|
||||
uploadedtotp = models.BigIntegerField(default=0)
|
||||
uploadedtorunkeeper = models.BigIntegerField(default=0)
|
||||
uploadedtonk = models.BigIntegerField(default=0)
|
||||
|
||||
@receiver(models.signals.pre_delete,sender=Workout)
|
||||
@@ -3294,9 +3282,7 @@ def create_tombstone_on_delete(sender, instance, **kwargs):
|
||||
user=instance.user,
|
||||
uploadedtoc2 = instance.uploadedtoc2,
|
||||
uploadedtostrava = instance.uploadedtostrava,
|
||||
uploadedtounderarmour = instance.uploadedtounderarmour,
|
||||
uploadedtotp = instance.uploadedtotp,
|
||||
uploadedtorunkeeper = instance.uploadedtorunkeeper,
|
||||
uploadedtonk = instance.uploadedtonk
|
||||
)
|
||||
t.save()
|
||||
@@ -3871,8 +3857,6 @@ class RowerExportForm(ModelForm):
|
||||
'c2_auto_export',
|
||||
'c2_auto_import',
|
||||
'nk_auto_import',
|
||||
'mapmyfitness_auto_export',
|
||||
'runkeeper_auto_export',
|
||||
'sporttracks_auto_export',
|
||||
'strava_auto_export',
|
||||
'strava_auto_import',
|
||||
@@ -4215,8 +4199,6 @@ class RowerImportExportForm(ModelForm):
|
||||
'polar_auto_import',
|
||||
'c2_auto_export',
|
||||
'c2_auto_import',
|
||||
'mapmyfitness_auto_export',
|
||||
'runkeeper_auto_export',
|
||||
'sporttracks_auto_export',
|
||||
'strava_auto_export',
|
||||
'strava_auto_import',
|
||||
|
||||
@@ -183,38 +183,6 @@ stcollection = (
|
||||
|
||||
stmapping = {key:value for key,value in Reverse(stcollection)}
|
||||
|
||||
rkcollection = (
|
||||
('water','Rowing'),
|
||||
('rower','Rowing'),
|
||||
('skierg','Cross-Country Skiing'),
|
||||
('bike','Cycling'),
|
||||
('bikeerg','Cycling'),
|
||||
('dynamic','Rowing'),
|
||||
('slides','Rowing'),
|
||||
('paddle','Other:Paddling'),
|
||||
('snow','Cross-Country Skiing'),
|
||||
('coastal','Rowing'),
|
||||
('c-boat','Rowing'),
|
||||
('churchboat','Rowing'),
|
||||
('Ride','Cycling'),
|
||||
('Run','Running'),
|
||||
('NordicSki','Cross-Country Skiing'),
|
||||
('Swim','Swimming'),
|
||||
('Hike','Hiking'),
|
||||
('Walk','Walking'),
|
||||
('Canoeing','Other'),
|
||||
('Crossfit','CrossFit'),
|
||||
('StandUpPaddling','Other'),
|
||||
('IceSkate','Skating'),
|
||||
('WeightTraining','Other'),
|
||||
('InlineSkate','Skating'),
|
||||
('Kayaking','Other'),
|
||||
('Workout','Other'),
|
||||
('other','Other'),
|
||||
('Yoga','Other'),
|
||||
)
|
||||
|
||||
rkmapping = {key:value for key,value in Reverse(rkcollection)}
|
||||
|
||||
polarcollection = (
|
||||
('water','Rowing'),
|
||||
@@ -320,7 +288,6 @@ stravamappinginv = {value:key for key,value in Reverse(stravacollection) if valu
|
||||
|
||||
stmappinginv = {value:key for key,value in Reverse(stcollection) if value is not None}
|
||||
|
||||
rkmappinginv = {value:key for key,value in Reverse(rkcollection) if value is not None}
|
||||
|
||||
polarmappinginv = {value:key for key,value in Reverse(polarcollection) if value is not None}
|
||||
|
||||
@@ -365,7 +332,6 @@ workoutsources = (
|
||||
('strava','strava'),
|
||||
('concept2','concept2'),
|
||||
('sporttracks','sporttracks'),
|
||||
('runkeeper','runkeeper'),
|
||||
('mapmyfitness','mapmyfitness'),
|
||||
('csv','painsled'),
|
||||
('tcx','tcx'),
|
||||
|
||||
@@ -35,7 +35,7 @@ def add_workout_from_data(userid,nkid,data,strokedata,source='nk',splitdata=None
|
||||
|
||||
try:
|
||||
userid=int(userid)
|
||||
except TypeError:
|
||||
except TypeError: # pragma: no cover
|
||||
userid = userid.id
|
||||
|
||||
strokedata.to_csv(csvfilename, index_label='index', compression='gzip')
|
||||
@@ -108,7 +108,7 @@ def add_workout_from_data(userid,nkid,data,strokedata,source='nk',splitdata=None
|
||||
|
||||
try:
|
||||
workoutid = response.json()['id']
|
||||
except KeyError:
|
||||
except KeyError: # pragma: no cover
|
||||
workoutid = 1
|
||||
|
||||
|
||||
|
||||
+18
-75
@@ -247,7 +247,7 @@ def get_nk_workout_list(user,fake=False,after=0,before=0):
|
||||
|
||||
#
|
||||
|
||||
def get_workout(user,nkid,do_async=False):
|
||||
def get_workout(user,nkid,do_async=True):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.nktoken == '') or (r.nktoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
@@ -260,79 +260,22 @@ def get_workout(user,nkid,do_async=False):
|
||||
'sessionIds': nkid,
|
||||
}
|
||||
|
||||
if do_async: # pragma: no cover
|
||||
res = get_nk_workout_list(r.user)
|
||||
if res.status_code != 200:
|
||||
return 0
|
||||
alldata = {}
|
||||
for item in res.json():
|
||||
alldata[item['id']] = item
|
||||
res = get_nk_workout_list(r.user)
|
||||
if res.status_code != 200: # pragma: no cover
|
||||
return 0
|
||||
alldata = {}
|
||||
for item in res.json():
|
||||
alldata[item['id']] = item
|
||||
|
||||
res = myqueue(
|
||||
queuehigh,
|
||||
handle_nk_async_workout,
|
||||
alldata,
|
||||
r.user.id,
|
||||
r.nktoken,
|
||||
nkid,
|
||||
0,
|
||||
r.defaulttimezone,
|
||||
)
|
||||
res = myqueue(
|
||||
queuehigh,
|
||||
handle_nk_async_workout,
|
||||
alldata,
|
||||
r.user.id,
|
||||
r.nktoken,
|
||||
nkid,
|
||||
0,
|
||||
r.defaulttimezone,
|
||||
)
|
||||
|
||||
return {},pd.DataFrame()
|
||||
|
||||
|
||||
authorizationstring = str('Bearer ' + r.nktoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
# get strokes
|
||||
url = NK_API_LOCATION+"api/v1/sessions/strokes"
|
||||
|
||||
response = requests.get(url,headers=headers,params=params)
|
||||
|
||||
if response.status_code != 200: # pragma: no cover
|
||||
# error handling and logging
|
||||
return {},pd.DataFrame()
|
||||
|
||||
jsonData = response.json()
|
||||
|
||||
strokeData = jsonData[str(nkid)]
|
||||
|
||||
df = strokeDataToDf(strokeData)
|
||||
|
||||
# get workout data
|
||||
after = df['timestamp'].min()
|
||||
before = df['timestamp'].max()
|
||||
|
||||
after = arrow.get(after/1000.)
|
||||
before = arrow.get(before/1000.)
|
||||
after = after-timedelta(days=1)
|
||||
before = before+timedelta(days=1)
|
||||
before = str(int(before.timestamp())*1000)
|
||||
after = str(int(after.timestamp())*1000)
|
||||
|
||||
|
||||
url = NK_API_LOCATION+"api/v1/sessions/"
|
||||
params = {
|
||||
'after':after,
|
||||
'before': before,
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers,params=params)
|
||||
|
||||
if response.status_code != 200: # pragma: no cover
|
||||
# error handling and logging
|
||||
return {},df
|
||||
|
||||
jsondata = response.json()
|
||||
workoutdata = {}
|
||||
|
||||
for w in jsondata:
|
||||
if str(w['id']) == str(nkid):
|
||||
workoutdata = w
|
||||
|
||||
|
||||
return workoutdata, df
|
||||
return res
|
||||
|
||||
@@ -1,530 +0,0 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
# All the functionality needed to connect to Runkeeper
|
||||
from rowers.imports import *
|
||||
import re
|
||||
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('low')
|
||||
from rowers.utils import myqueue
|
||||
|
||||
from rowers.rower_rules import is_workout_user
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||
RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET,RUNKEEPER_REDIRECT_URI,
|
||||
)
|
||||
|
||||
from rowers.tasks import handle_runkeeper_sync
|
||||
|
||||
oauth_data = {
|
||||
'client_id': RUNKEEPER_CLIENT_ID,
|
||||
'client_secret': RUNKEEPER_CLIENT_SECRET,
|
||||
'redirect_uri': RUNKEEPER_REDIRECT_URI,
|
||||
'autorization_uri': "https://www.runkeeper.com/apps/authorize",
|
||||
'content_type': 'application/x-www-form-urlencoded',
|
||||
'tokenname': 'runkeepertoken',
|
||||
'refreshtokenname': None,
|
||||
'expirydatename': None,
|
||||
'bearer_auth': True,
|
||||
'base_url': "https://runkeeper.com/apps/token",
|
||||
'headers': {'user-agent': 'sanderroosendaal'},
|
||||
'scope':'write',
|
||||
}
|
||||
|
||||
|
||||
import numpy
|
||||
|
||||
def splitrunkeeperlatlongdata(lijst,tname,latname,lonname):
|
||||
t = []
|
||||
lat = []
|
||||
lon = []
|
||||
for d in lijst:
|
||||
t.append(d[tname])
|
||||
lat.append(d[latname])
|
||||
lon.append(d[lonname])
|
||||
|
||||
return [np.array(t),np.array(lat),np.array(lon)]
|
||||
|
||||
def splitrunkeeperdata(lijst,xname,yname):
|
||||
x = []
|
||||
y = []
|
||||
for d in lijst:
|
||||
x.append(d[xname])
|
||||
y.append(d[yname])
|
||||
|
||||
return [np.array(x),np.array(y)]
|
||||
|
||||
|
||||
# Checks if user has SportTracks token, renews them if they are expired
|
||||
def runkeeper_open(user):
|
||||
return imports_open(user,oauth_data)
|
||||
|
||||
# Exchange access code for long-lived access token
|
||||
def get_token(code):
|
||||
return imports_get_token(code,oauth_data)
|
||||
|
||||
# Make authorization URL including random string
|
||||
def make_authorization_url(request): # pragma: no cover
|
||||
return imports_make_authorization_url(oauth_data)
|
||||
|
||||
# Get list of workouts available on Runkeeper
|
||||
def get_runkeeper_workout_list(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.runkeepertoken == '') or (r.runkeepertoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s)
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.runkeepertoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.runkeeper.com/fitnessActivities"
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
return s
|
||||
|
||||
# Get workout summary data by Runkeeper ID
|
||||
def get_workout(user,runkeeperid,do_async=False):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.runkeepertoken == '') or (r.runkeepertoken is None): # pragma: no cover
|
||||
return custom_exception_handler(401,s)
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.runkeepertoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.runkeeper.com/fitnessActivities/"+str(runkeeperid)
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
try:
|
||||
data = s.json()
|
||||
except ValueError: # pragma: no cover
|
||||
data = {}
|
||||
return data,"Something went wrong with the workout import"
|
||||
|
||||
strokedata = pd.DataFrame.from_dict({
|
||||
key: pd.Series(value) for key, value in data.items()
|
||||
})
|
||||
|
||||
return data,strokedata
|
||||
|
||||
# Create Workout Data for upload to SportTracks
|
||||
def createrunkeeperworkoutdata(w):
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except: # pragma: no cover
|
||||
return 0
|
||||
|
||||
try:
|
||||
averagehr = int(row.df[' HRCur (bpm)'].mean())
|
||||
maxhr = int(row.df[' HRCur (bpm)'].max())
|
||||
except KeyError: # pragma: no cover
|
||||
averagehr = 0
|
||||
maxhr = 0
|
||||
|
||||
duration = w.duration.hour*3600
|
||||
duration += w.duration.minute*60
|
||||
duration += w.duration.second
|
||||
duration += +1.0e-6*w.duration.microsecond
|
||||
|
||||
try:
|
||||
t = row.df.loc[:,'TimeStamp (sec)'].values-row.df.loc[:,'TimeStamp (sec)'].iloc[0]
|
||||
except KeyError: # pragma: no cover
|
||||
return pd.DataFrame()
|
||||
|
||||
t[0] = t[1]
|
||||
|
||||
d = row.df.loc[:,'cum_dist'].values
|
||||
d[0] = d[1]
|
||||
t = t.astype(int)
|
||||
d = d.astype(int)
|
||||
spm = row.df[' Cadence (stokes/min)'].astype(int)
|
||||
spm[0] = spm[1]
|
||||
hr = row.df[' HRCur (bpm)'].astype(int)
|
||||
|
||||
haslatlon=1
|
||||
|
||||
try:
|
||||
lat = row.df[' latitude'].values
|
||||
lon = row.df[' longitude'].values
|
||||
if not lat.std() and not lon.std(): # pragma: no cover
|
||||
haslatlon = 0
|
||||
except KeyError: # pragma: no cover
|
||||
haslatlon = 0
|
||||
|
||||
t = t.tolist()
|
||||
hr = hr.tolist()
|
||||
d = d.tolist()
|
||||
|
||||
# path data
|
||||
if haslatlon:
|
||||
lat = lat.tolist()
|
||||
lon = lon.tolist()
|
||||
locdata = []
|
||||
for e in zip(t,lat,lon):
|
||||
point = {'timestamp':e[0],
|
||||
'latitude':e[1],
|
||||
'longitude':e[2],
|
||||
'altitude':0,
|
||||
"type":"gps"}
|
||||
locdata.append(point)
|
||||
|
||||
hrdata = []
|
||||
for e in zip(t,hr):
|
||||
point = {'timestamp':e[0],
|
||||
'heart_rate':e[1]
|
||||
}
|
||||
hrdata.append(point)
|
||||
|
||||
distancedata = []
|
||||
for e in zip(t,d):
|
||||
point = {'timestamp':e[0],
|
||||
'distance':e[1]
|
||||
}
|
||||
distancedata.append(point)
|
||||
|
||||
st = w.startdatetime.astimezone(pytz.timezone(w.timezone))
|
||||
start_time = st.strftime("%a, %d %b %Y %H:%M:%S")
|
||||
|
||||
try:
|
||||
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
|
||||
except TypeError:
|
||||
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
|
||||
|
||||
if haslatlon:
|
||||
data = {
|
||||
"type": "Rowing",
|
||||
"start_time": start_time,
|
||||
"total_distance": int(w.distance),
|
||||
"duration": duration,
|
||||
"notes": newnotes,
|
||||
"average_heart_rate": averagehr,
|
||||
"path": locdata,
|
||||
"distance": distancedata,
|
||||
"heart_rate": hrdata,
|
||||
"post_to_twitter":"false",
|
||||
"post_to_facebook":"false",
|
||||
}
|
||||
else:
|
||||
data = {
|
||||
"type": "Rowing",
|
||||
"start_time": start_time,
|
||||
"total_distance": int(w.distance),
|
||||
"duration": duration,
|
||||
"notes": newnotes,
|
||||
"avg_heartrate": averagehr,
|
||||
"distance": distancedata,
|
||||
"heart_rate": hrdata,
|
||||
"post_to_twitter":"false",
|
||||
"post_to_facebook":"false",
|
||||
}
|
||||
|
||||
|
||||
return data
|
||||
|
||||
# Obtain Runkeeper Workout ID from the response returned on successful
|
||||
# upload
|
||||
def getidfromresponse(response):
|
||||
uri = response.headers["Location"]
|
||||
|
||||
tester = re.compile('^\/fitnessActivities\/(\d+)$')
|
||||
id = int(tester.match(uri).group(1))
|
||||
|
||||
return int(id)
|
||||
|
||||
def geturifromid(access_token,id): # pragma: no cover
|
||||
authorizationstring = str('Bearer ' + access_token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
import urllib
|
||||
url = "https://api.runkeeper.com/fitnessActivities/"+str(id)
|
||||
response = requests.get(url,headers=headers)
|
||||
try:
|
||||
me_json = response.json()
|
||||
except:
|
||||
return ''
|
||||
|
||||
try:
|
||||
res = me_json['uri']
|
||||
except KeyError:
|
||||
res = ''
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# Get user id, having access token
|
||||
# Handy for checking if the API access is working
|
||||
def get_userid(access_token):
|
||||
authorizationstring = str('Bearer ' + access_token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
import urllib
|
||||
url = "https://api.runkeeper.com/user"
|
||||
response = requests.get(url,headers=headers)
|
||||
|
||||
|
||||
try:
|
||||
me_json = response.json()
|
||||
except: # pragma: no cover
|
||||
return ''
|
||||
|
||||
try:
|
||||
res = me_json['userID']
|
||||
except KeyError: # pragma: no cover
|
||||
res = ''
|
||||
|
||||
return str(res)
|
||||
|
||||
def default(o): # pragma: no cover
|
||||
if isinstance(o, numpy.int64): return int(o)
|
||||
raise TypeError
|
||||
|
||||
def workout_runkeeper_upload(user,w,asynchron=False): # pragma: no cover
|
||||
message = "Uploading to Runkeeper"
|
||||
rkid = 0
|
||||
|
||||
r = w.user
|
||||
|
||||
|
||||
thetoken = runkeeper_open(r.user)
|
||||
|
||||
# ready to upload. Hurray
|
||||
|
||||
if (is_workout_user(user,w)):
|
||||
data = createrunkeeperworkoutdata(w)
|
||||
if not data:
|
||||
message = "Data error in Runkeeper Upload"
|
||||
rkid = 0
|
||||
return message, rkid
|
||||
|
||||
authorizationstring = str('Bearer ' + thetoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json',
|
||||
'Content-Length':'nnn'}
|
||||
|
||||
url = "https://api.runkeeper.com/fitnessActivities"
|
||||
if asynchron:
|
||||
job = myqueue(queue,handle_runkeeper_sync,
|
||||
w.id,url,headers,json.dumps(data,default=default))
|
||||
return "Asynchronous sync",0
|
||||
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
|
||||
|
||||
# check for duplicate error first
|
||||
if (response.status_code == 409 ):
|
||||
message = "Duplicate error"
|
||||
w.uploadedtorunkeeper = -1
|
||||
rkid = -1
|
||||
w.save()
|
||||
return message, rkid
|
||||
elif (response.status_code == 201 or response.status_code==200):
|
||||
rkid = getidfromresponse(response)
|
||||
rkuri = geturifromid(thetoken,rkid)
|
||||
w.uploadedtorunkeeper = rkid
|
||||
w.save()
|
||||
return 'Successfully synchronized to Runkeeper',rkid
|
||||
else:
|
||||
s = response
|
||||
message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text)
|
||||
rkid = 0
|
||||
return message, rkid
|
||||
|
||||
else:
|
||||
message = "You are not authorized to upload this workout"
|
||||
rkid = 0
|
||||
return message, rkid
|
||||
|
||||
return message,rkid
|
||||
|
||||
# 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: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
times_location = times_distance
|
||||
latcoord = np.zeros(len(times_distance))
|
||||
loncoord = np.zeros(len(times_distance))
|
||||
if workouttype in types.otwtypes:
|
||||
workouttype = 'rower'
|
||||
|
||||
try: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
latseries = 0.0*distseries
|
||||
|
||||
lonseries = pd.Series(loncoord,index=times_location)
|
||||
try:
|
||||
lonseries = lonseries.groupby(lonseries.index).first()
|
||||
except TypeError: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
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,
|
||||
dosmooth=r.dosmooth,
|
||||
notes=comments)
|
||||
|
||||
return (id,message)
|
||||
@@ -120,7 +120,14 @@ def get_workout(user,sporttracksid,do_async=False):
|
||||
key: pd.Series(value) for key, value in data.items()
|
||||
})
|
||||
|
||||
return data,strokedata
|
||||
id,message = add_workout_from_data(
|
||||
user,
|
||||
sporttracksid,data,
|
||||
strokedata,
|
||||
source='sporttracks',
|
||||
workoutsource='sporttracks')
|
||||
|
||||
return id
|
||||
|
||||
# Create Workout Data for upload to SportTracks
|
||||
def createsporttracksworkoutdata(w):
|
||||
@@ -252,7 +259,7 @@ def createsporttracksworkoutdata(w):
|
||||
|
||||
# Obtain SportTracks Workout ID from the response returned on successful
|
||||
# upload
|
||||
def getidfromresponse(response):
|
||||
def getidfromresponse(response): # pragma: no cover
|
||||
t = response.json()
|
||||
uri = t['uris'][0]
|
||||
regex = '.*?sporttracks\.mobi\/api\/v2\/fitnessActivities/(\d+)\.json$'
|
||||
|
||||
+2
-267
@@ -382,118 +382,9 @@ def async_get_workout(user,stravaid):
|
||||
return job
|
||||
|
||||
# Get a Strava workout summary data and stroke data by ID
|
||||
def get_workout(user,stravaid,do_async=False):
|
||||
if do_async: # pragma: no cover
|
||||
res = async_get_workout(user,stravaid)
|
||||
return {},pd.DataFrame()
|
||||
try:
|
||||
thetoken = strava_open(user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
s = "Token error"
|
||||
return custom_exception_handler(401,s)
|
||||
def get_workout(user,stravaid,do_async=True):
|
||||
return async_get_workout(user,stravaid)
|
||||
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s)
|
||||
elif (r.stravatokenexpirydate is not None and timezone.now()>r.stravatokenexpirydate): # pragma: no cover
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401,s)
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
fetchresolution = 'high'
|
||||
series_type = 'time'
|
||||
authorizationstring = str('Bearer ' + r.stravatoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json',
|
||||
'resolution': 'medium',}
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)
|
||||
workoutsummary = requests.get(url,headers=headers).json()
|
||||
|
||||
workoutsummary['timezone'] = "Etc/UTC"
|
||||
try:
|
||||
startdatetime = workoutsummary['start_date']
|
||||
except KeyError: # pragma: no cover
|
||||
startdatetime = timezone.now()
|
||||
|
||||
spm = get_strava_stream(r,'cadence',stravaid)
|
||||
hr = get_strava_stream(r,'heartrate',stravaid)
|
||||
t = get_strava_stream(r,'time',stravaid)
|
||||
velo = get_strava_stream(r,'velocity_smooth',stravaid)
|
||||
d = get_strava_stream(r,'distance',stravaid)
|
||||
coords = get_strava_stream(r,'latlng',stravaid)
|
||||
power = get_strava_stream(r,'watts',stravaid)
|
||||
|
||||
if t is not None:
|
||||
nr_rows = len(t)
|
||||
else: # pragma: no cover
|
||||
duration = int(workoutsummary['elapsed_time'])
|
||||
t = pd.Series(range(duration+1))
|
||||
|
||||
nr_rows = len(t)
|
||||
|
||||
|
||||
if nr_rows == 0: # pragma: no cover
|
||||
return (0,"Error: Time data had zero length")
|
||||
|
||||
if d is None: # pragma: no cover
|
||||
d = 0*t
|
||||
|
||||
if spm is None: # pragma: no cover
|
||||
spm = np.zeros(nr_rows)
|
||||
|
||||
if power is None: # pragma: no cover
|
||||
power = np.zeros(nr_rows)
|
||||
|
||||
if hr is None: # pragma: no cover
|
||||
hr = np.zeros(nr_rows)
|
||||
|
||||
if velo is None: # pragma: no cover
|
||||
velo = np.zeros(nr_rows)
|
||||
|
||||
dt = np.diff(t).mean()
|
||||
wsize = round(5./dt)
|
||||
|
||||
velo2 = ewmovingaverage(velo,wsize)
|
||||
|
||||
if coords is not None:
|
||||
try:
|
||||
lat = coords[:,0]
|
||||
lon = coords[:,1]
|
||||
except IndexError: # pragma: no cover
|
||||
lat = np.zeros(len(t))
|
||||
lon = np.zeros(len(t))
|
||||
else: # pragma: no cover
|
||||
lat = np.zeros(len(t))
|
||||
lon = np.zeros(len(t))
|
||||
|
||||
|
||||
|
||||
|
||||
strokelength = velo*60./(spm)
|
||||
strokelength[np.isinf(strokelength)] = 0.0
|
||||
|
||||
|
||||
pace = 500./(1.0*velo2)
|
||||
pace[np.isinf(pace)] = 0.0
|
||||
|
||||
df = pd.DataFrame({'t':10*t,
|
||||
'd':10*d,
|
||||
'p':10*pace,
|
||||
'spm':spm,
|
||||
'hr':hr,
|
||||
'lat':lat,
|
||||
'lon':lon,
|
||||
'power':power,
|
||||
'strokelength':strokelength,
|
||||
})
|
||||
|
||||
|
||||
|
||||
# startdatetime = datetime.datetime.strptime(startdatetime,"%Y-%m-%d-%H:%M:%S")
|
||||
|
||||
return [workoutsummary,df]
|
||||
|
||||
# Generate Workout data for Strava (a TCX file)
|
||||
def createstravaworkoutdata(w,dozip=True):
|
||||
@@ -577,162 +468,6 @@ def handle_stravaexport(f2,workoutname,stravatoken,description='',
|
||||
|
||||
return (res.id,message)
|
||||
|
||||
# Create workout data from Strava or Concept2
|
||||
# data and create the associated Workout object and save it
|
||||
def add_workout_from_data(user,importid,data,strokedata,
|
||||
source='strava',splitdata=None,
|
||||
workoutsource='strava'):
|
||||
try:
|
||||
workouttype = mytypes.stravamappinginv[data['type']]
|
||||
except KeyError: # pragma: no cover
|
||||
workouttype = 'other'
|
||||
|
||||
if workouttype.lower() == 'rowing': # pragma: no cover
|
||||
workouttype = 'rower'
|
||||
|
||||
if 'summary_polyline' in data['map'] and workouttype=='rower': # pragma: no cover
|
||||
workouttype = 'water'
|
||||
|
||||
if workouttype not in [x[0] for x in Workout.workouttypes]: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
rowdatetime = iso8601.parse_date(data['date'])
|
||||
|
||||
|
||||
try:
|
||||
intervaltype = data['workout_type']
|
||||
|
||||
except KeyError:
|
||||
intervaltype = ''
|
||||
|
||||
try:
|
||||
title = data['name']
|
||||
except KeyError: # pragma: no cover
|
||||
title = ""
|
||||
try:
|
||||
t = data['comments'].split('\n', 1)[0]
|
||||
title += t[:20]
|
||||
except:
|
||||
title = ''
|
||||
|
||||
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.loc[:,'t']
|
||||
|
||||
nr_rows = len(unixtime)
|
||||
|
||||
try:
|
||||
latcoord = strokedata.loc[:,'lat']
|
||||
loncoord = strokedata.loc[:,'lon']
|
||||
if latcoord.std() == 0 and loncoord.std() == 0 and workouttype == 'water': # pragma: no cover
|
||||
workouttype = 'rower'
|
||||
except: # pragma: no cover
|
||||
latcoord = np.zeros(nr_rows)
|
||||
loncoord = np.zeros(nr_rows)
|
||||
if workouttype == 'water':
|
||||
workouttype = 'rower'
|
||||
|
||||
|
||||
|
||||
try:
|
||||
strokelength = strokedata.loc[:,'strokelength']
|
||||
except: # pragma: no cover
|
||||
strokelength = np.zeros(nr_rows)
|
||||
|
||||
dist2 = 0.1*strokedata.loc[:,'d']
|
||||
|
||||
try:
|
||||
spm = strokedata.loc[:,'spm']
|
||||
except KeyError: # pragma: no cover
|
||||
spm = 0*dist2
|
||||
|
||||
try:
|
||||
hr = strokedata.loc[:,'hr']
|
||||
except KeyError: # pragma: no cover
|
||||
hr = 0*spm
|
||||
pace = strokedata.loc[:,'p']/10.
|
||||
pace = np.clip(pace,0,1e4)
|
||||
pace = pace.replace(0,300)
|
||||
|
||||
velo = 500./pace
|
||||
|
||||
try:
|
||||
power = strokedata.loc[:,'power']
|
||||
except KeyError: # pragma: no cover
|
||||
power = 2.8*velo**3
|
||||
|
||||
#if power.std() == 0 and power.mean() == 0:
|
||||
# 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,
|
||||
dosmooth=r.dosmooth,
|
||||
workouttype=workouttype,
|
||||
title=title,notes=comments,
|
||||
workoutsource=workoutsource,
|
||||
dosummary=True
|
||||
)
|
||||
|
||||
|
||||
return id,message
|
||||
|
||||
def workout_strava_upload(user,w, quick=False,asynchron=True):
|
||||
try:
|
||||
|
||||
+26
-52
@@ -25,7 +25,6 @@ from rowingdata import rowingdata as rdata
|
||||
from datetime import timedelta
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from simplejson.errors import JSONDecodeError
|
||||
|
||||
#from celery import app
|
||||
from rowers.celery import app
|
||||
@@ -66,7 +65,8 @@ from django_rq import job
|
||||
from django.utils import timezone
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
from rowers.utils import deserialize_list,ewmovingaverage,wavg
|
||||
from rowers.utils import deserialize_list,ewmovingaverage,wavg,dologging
|
||||
import rowers.utils as utils
|
||||
from rowers.emails import htmlstrip
|
||||
from rowers import mytypes
|
||||
|
||||
@@ -127,15 +127,6 @@ from rowers.courseutils import (
|
||||
InvalidTrajectoryError
|
||||
)
|
||||
|
||||
def dologging(s):
|
||||
tstamp = time.localtime()
|
||||
timestamp = time.strftime('%b-%d-%Y %H:%M:%S', tstamp)
|
||||
with open('debuglog.log','a') as f:
|
||||
f.write('\n')
|
||||
f.write(timestamp)
|
||||
f.write(' ')
|
||||
f.write(s)
|
||||
|
||||
# Concept2 logbook sends over split data for each interval
|
||||
# We use it here to generate a custom summary
|
||||
# Some users complained about small differences
|
||||
@@ -327,20 +318,6 @@ def handle_sporttracks_sync(workoutid,url,headers,data,debug=False,**kwargs):
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_runkeeper_sync(workoutid,url,headers,data,debug=False,**kwargs):
|
||||
response = requests.post(url,headers=headers,data=data)
|
||||
if response.status_code not in [200,201]: # pragma: no cover
|
||||
return 0
|
||||
|
||||
uri = response.headers["Location"]
|
||||
|
||||
tester = re.compile('^\/fitnessActivities\/(\d+)$')
|
||||
id = int(tester.match(uri).group(1))
|
||||
|
||||
res = update_workout_field_sql(workoutid,'uploadedtorunkeeper',id,debug=debug)
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_strava_sync(stravatoken,workoutid,filename,name,activity_type,description,debug=False,**kwargs):
|
||||
@@ -2850,7 +2827,7 @@ def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,max_attempts,de
|
||||
|
||||
try:
|
||||
workout_download_details = pd.json_normalize(response.json()['data']['download'])
|
||||
except JSONDecodeError:
|
||||
except JSONDecodeError: # pragma: no cover
|
||||
return 0
|
||||
|
||||
if workout_download_details.iat[0,1] == 'ready':
|
||||
@@ -2990,7 +2967,23 @@ def handle_nk_async_workout(alldata,userid,nktoken,nkid,delaysec,defaulttimezone
|
||||
# return
|
||||
return workoutid
|
||||
|
||||
@app.task
|
||||
def handle_c2_getworkout(userid,c2token,c2id,defaulttimezone,debug=False,**kwargs):
|
||||
authorizationstring = str('Bearer ' + c2token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://log.concept2.com/api/users/me/results/"+str(c2id)
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
if s.status_code != 200: # pragma: no cover
|
||||
return 0
|
||||
|
||||
data = s.json()['data']
|
||||
alldata = {c2id:data}
|
||||
splitdata = None
|
||||
|
||||
return handle_c2_async_workout(alldata,userid,c2token,c2id,0,defaulttimezone)
|
||||
|
||||
@app.task
|
||||
def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone,debug=False,**kwargs):
|
||||
@@ -3005,15 +2998,12 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
|
||||
c2id = data['id']
|
||||
workouttype = data['type']
|
||||
verified = data['verified']
|
||||
try:
|
||||
startdatetime = iso8601.parse_date(data['date_utc'])
|
||||
except: # pragma: no cover
|
||||
startdatetime = iso8601.parse_date(data['date'])
|
||||
|
||||
weightclass = data['weight_class']
|
||||
|
||||
s = 'User {userid}, C2 ID {c2id}'.format(userid=userid,c2id=c2id)
|
||||
dologging(s)
|
||||
dologging(json.dumps(data))
|
||||
dologging('debuglog.log',s)
|
||||
dologging('debuglog.log',json.dumps(data))
|
||||
|
||||
try:
|
||||
title = data['name']
|
||||
@@ -3032,30 +3022,13 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
|
||||
# Create CSV file name and save data to CSV file
|
||||
csvfilename ='media/Import_'+str(c2id)+'.csv.gz'
|
||||
|
||||
totaltime = data['time']/10.
|
||||
duration = totaltime_sec_to_string(totaltime)
|
||||
starttimeunix = arrow.get(startdatetime).timestamp()-totaltime
|
||||
startdatetime = arrow.get(starttimeunix)
|
||||
startdatetime,starttime,workoutdate,duration,starttimeunix,timezone = utils.get_startdatetime_from_c2data(data)
|
||||
|
||||
try:
|
||||
timezone = pytz.timezone(data['timezone'])
|
||||
except UnknownTimeZoneError:
|
||||
timezone = pytz.utc
|
||||
startdatetime = startdatetime.astimezone(timezone)
|
||||
|
||||
s = 'Time zone {timezone}, stardatetime {startdatetime}, duration {duration}'.format(
|
||||
timezone=timezone,startdatetime=startdatetime,
|
||||
duration=duration)
|
||||
dologging(s)
|
||||
|
||||
|
||||
|
||||
workoutdate = startdatetime.astimezone(
|
||||
timezone
|
||||
).strftime('%Y-%m-%d')
|
||||
starttime = startdatetime.astimezone(
|
||||
timezone
|
||||
).strftime('%H:%M:%S')
|
||||
dologging('debuglog.log',s)
|
||||
|
||||
try:
|
||||
notes = data['comments']
|
||||
@@ -3240,7 +3213,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
|
||||
units = getlist(sa,sel='unit')
|
||||
types = getlist(sa,sel='type')
|
||||
|
||||
rowdata = rdata(csvfilename)
|
||||
rowdata = rdata(csvfile=csvfilename)
|
||||
if rowdata:
|
||||
rowdata.updateintervaldata(values,
|
||||
units,types,results)
|
||||
@@ -3251,6 +3224,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
|
||||
return workoutid
|
||||
|
||||
|
||||
|
||||
@app.task
|
||||
def fetch_strava_workout(stravatoken,oauth_data,stravaid,csvfilename,userid,debug=False,**kwargs):
|
||||
fetchresolution = 'high'
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Export {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="exportbuttons" class="grid_6 alpha">
|
||||
|
||||
|
||||
<h3>Export Workout</h3>
|
||||
|
||||
<div class="grid_2 alpha">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/edit">Edit Workout</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/workflow">Workflow View</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/advanced">Advanced Edit</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<p>
|
||||
Click on the icon to upload this workout to your site of choice. A checkmark indicates that the workout has already been uploaded. If the button is grayed out, click it to authorize the connection to that site. Use TCX or CSV export to email a TCX or CSV file of your workout to yourself.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
{% if workout.uploadedtoc2 == 0 %}
|
||||
{% if user.rower.c2token == None or user.rower.c2token == '' %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="/rowers/me/c2authorize">
|
||||
<img src="/static/img/c2square_gray.png" alt="C2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/c2uploadw"><img src="/static/img/c2square.jpg" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="http://log.concept2.com/profile/{{ c2userid }}/log/{{ workout.uploadedtoc2 }}">
|
||||
<img src="/static/img/c2square_checked.png" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if workout.uploadedtostrava == 0 %}
|
||||
{% if user.rower.stravatoken == None or user.rower.stravatoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/stravaauthorize">
|
||||
<img src="/static/img/stravasquare_gray.png" alt="Strava icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/stravauploadw"><img src="/static/img/stravasquare.png" alt="Strava icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="https://www.strava.com/activities/{{ workout.uploadedtostrava }}">
|
||||
<img src="/static/img/stravasquare_checked.png" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if workout.uploadedtosporttracks == 0 %}
|
||||
{% if user.rower.sporttrackstoken == None or user.rower.sporttrackstoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/sporttracksauthorize">
|
||||
<img src="/static/img/sporttrackssquare_gray.png" alt="SportTracks icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/sporttracksuploadw">
|
||||
<img src="/static/img/sporttrackssquare.png" alt="SportTracks icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="https://sporttracks.mobi/activity/{{ workout.uploadedtosporttracks }}">
|
||||
<img src="/static/img/sporttrackssquare_checked.png" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/emailtcx">
|
||||
<img src="/static/img/export.png" alt="TCX Export" width="60" height="60"></a>
|
||||
</div>
|
||||
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/emailcsv">
|
||||
<img src="/static/img/CSVsquare.png" alt="CSV Export" width="60" height="60"></a>
|
||||
</div>
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
{% if workout.uploadedtorunkeeper == 0 %}
|
||||
{% if user.rower.runkeepertoken == None or user.rower.runkeepertoken == '' %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="/rowers/me/runkeeperauthorize">
|
||||
<img src="/static/img/rkgray.png" alt="Runkeeper icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/runkeeperuploadw"><img src="/static/img/rksquare.png" alt="Runkeeper icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="https://runkeeper.com/user/{{ rkuserid }}/activity/{{ workout.uploadedtorunkeeper }}">
|
||||
<img src="/static/img/rkchecked.png" alt="Runkeeper icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if workout.uploadedtounderarmour == 0 %}
|
||||
{% if user.rower.underarmourtoken == None or user.rower.underarmourtoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/underarmourauthorize">
|
||||
<img src="/static/img/uagray.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/underarmouruploadw"><img src="/static/img/uasquare.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="https://www.mapmyfitness.com/workout/{{ workout.uploadedtounderarmour }}">
|
||||
<img src="/static/img/uachecked.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if workout.uploadedtotp == 0 %}
|
||||
{% if user.rower.tptoken == None or user.rower.tptoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/tpauthorize">
|
||||
<img src="/static/img/tpgray.png" alt="TrainingPeaks icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/tpuploadw"><img src="/static/img/tpicon.png" alt="Tp icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="https://app.trainingpeaks.com">
|
||||
<img src="/static/img/tpchecked.png" alt="TrainingPeaks icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid_1 omega">
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/emailgpx">
|
||||
<img src="/static/img/gpx.jpg" alt="GPX Export" width="60" height="60"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid_6 omega">
|
||||
<h3>Connect</h3>
|
||||
|
||||
<div class="grid_6">
|
||||
<p>Click one of the below logos to connect to the service of your choice.
|
||||
You only need to do this once. After that, the site will have access until you
|
||||
revoke the authorization for the "rowingdata" app.</p>
|
||||
|
||||
<div class="grid_2 alpha">
|
||||
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
|
||||
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid_2 omega">
|
||||
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="grid_6">
|
||||
<div class="grid_2 alpha">
|
||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with Runkeeper" width="120"></a></p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png" alt="connect with TrainingPeaks" width="150"></a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_6 alpha">
|
||||
<p>
|
||||
<h2>Export Settings</h2>
|
||||
<div class="grid_2 suffix_4 alpha">
|
||||
<a class="button gray small" href="/rowers/me/exportsettings">Manage Export Settings</a>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,137 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Import Workouts{% endblock title %}
|
||||
{% block content %}
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<h2>Import Workouts</h2>
|
||||
|
||||
<div class="grid_6">
|
||||
<div class="grid_3 alpha">
|
||||
<p>
|
||||
<a href="/rowers/workout/stravaimport"><img src="/static/img/stravalogo.png" alt="strava logo" width="140"></a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_3 omega">
|
||||
<p>Import workouts from Strava</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_6">
|
||||
<div class="grid_3 alpha">
|
||||
<p>
|
||||
<a href="/rowers/workout/c2list"><img src="/static/img/blueC2logo.png" alt="Concept2 logo" width="140"></a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_3 omega">
|
||||
<p>Import workouts from the Concept2 logbook</p>
|
||||
</div>
|
||||
<div class="grid_3 alpha">
|
||||
<p>
|
||||
<a href="/rowers/workout/sporttracksimport"><img src="/static/img/sporttracks-button.png" alt="SportTracks logo" width="140"></a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_3 omega">
|
||||
<p>Import workouts from SportTracks</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_6">
|
||||
<div class="grid_3 alpha">
|
||||
<p>
|
||||
<a href="/rowers/workout/runkeeperimport"><img src="/static/img/rk-logo.png" alt="Runkeeper logo" width="140"></a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_3 omega">
|
||||
<p>Import workouts from RunKeeper</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_6">
|
||||
<div class="grid_3 alpha">
|
||||
<p>
|
||||
<a href="/rowers/workout/underarmourimport"><img src="/static/img/UAbtn.png" alt="Under Armour logo" width="140"></a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_3 omega">
|
||||
<p>Import workouts from MapMyFitness/UnderArmour</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_6">
|
||||
<div class="grid_3 alpha">
|
||||
<p>
|
||||
<a href="/rowers/workout/polarimport"><img src="/static/img/Polar_connectwith_btn_white.png" alt="Polar logo" width="140"></a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_3 omega">
|
||||
<p>Import workouts from Polar Flow. </p><p><b>Note: No workout selection possible. Automatically imports all new workouts</b></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid_6 omega">
|
||||
<h2>Connect</h2>
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<p>Click one of the below logos to connect to the service of your choice.
|
||||
You only need to do this once. After that, the site will have
|
||||
access until you
|
||||
revoke the authorization for the "rowingdata" app.</p>
|
||||
|
||||
<div class="grid_2 alpha">
|
||||
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
|
||||
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid_2 omega">
|
||||
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="grid_6 alpha">
|
||||
<div class="grid_2 alpha">
|
||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
<p><a href="/rowers/me/polarauthorize/"><img src="/static/img/Polar_connectwith_btn_white.png"
|
||||
alt="connect with Polar" width="130"></a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Auto Import/Export Settings</h2>
|
||||
|
||||
|
||||
<p>Use the form below to set your auto import/export settings.
|
||||
As we implement auto import/export for various partner sites, this form will be expanded.</p>
|
||||
|
||||
<p>Auto Import = rowsandall.com will regularly poll the partner site for new
|
||||
workouts and automatically import new workouts
|
||||
</p>
|
||||
|
||||
<p>Auto Export = New workouts uploaded to rowsandall.com will be automatically synchronized
|
||||
to the partner site</p>
|
||||
|
||||
<p>These settings only have effect if you are a user on one
|
||||
of the <a href="/rowers/paidplans">paid plans</a></p>
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<form id="importexportform" method="post">
|
||||
<table width=70%>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<div id="formbutton">
|
||||
<input type="submit" value="Save">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<li id="strava"><a href="/rowers/workout/stravaimport/">Strava</a></li>
|
||||
<li id="runkeeper"><a href="/rowers/workout/runkeeperimport/">RunKeeper</a></li>
|
||||
<li id="sporttracks"><a href="/rowers/workout/sporttracksimport/">SportTracks</a></li>
|
||||
<li id="mapmyfitness"><a href="/rowers/workout/underarmourimport/">MapMyFitness</a></li>
|
||||
<li id="polar"><a href="/rowers/workout/polarimport/">Polar</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -192,36 +192,6 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li id="export-rk">
|
||||
{% if workout.uploadedtorunkeeper %}
|
||||
<a href="https://runkeeper.com/user/{{ user|rkuserid }}/activity/{{ workout.uploadedtorunkeeper }}">
|
||||
Runkeeper <i class="fas fa-check"></i>
|
||||
</a>
|
||||
{% elif user.rower.runkeepertoken == None or user.rower.runkeepertoken == '' %}
|
||||
<a href="/rowers/me/runkeeperauthorize">
|
||||
Connect to Runkeeper
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/runkeeperuploadw/">
|
||||
Runkeeper
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li id="export-mmf">
|
||||
{% if workout.uploadedtounderarmour %}
|
||||
<a href="https://www.mapmyfitness.com/workout/{{ workout.uploadedtounderarmour }}">
|
||||
MapMyFitness <i class="fas fa-check"></i>
|
||||
</a>
|
||||
{% elif user.rower.underarmourtoken == None or user.rower.underarmourtoken == '' %}
|
||||
<a href="/rowers/me/underarmourauthorize">
|
||||
Connect to MapMyFitness
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/underarmouruploadw/">
|
||||
MapMyFitness
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li id="export-tp">
|
||||
{% if workout.uploadedtotp %}
|
||||
<a href="https://app.trainingpeaks.com">
|
||||
|
||||
@@ -40,9 +40,7 @@
|
||||
<li id="concept2"><a href="/rowers/workout/c2list/">Concept2</a></li>
|
||||
<li id="nklink"><a href="/rowers/workout/nkimport/">NK Logbook</a></li>
|
||||
<li id="strava"><a href="/rowers/workout/stravaimport/">Strava</a></li>
|
||||
<li id="runkeeper"><a href="/rowers/workout/runkeeperimport/">RunKeeper</a></li>
|
||||
<li id="sporttracks"><a href="/rowers/workout/sporttracksimport/">SportTracks</a></li>
|
||||
<li id="mapmyfitness"><a href="/rowers/workout/underarmourimport/">MapMyFitness</a></li>
|
||||
<li id="polar"><a href="/rowers/workout/polarimport/">Polar</a></li>
|
||||
<li id="rp3"><a href="/rowers/workout/rp3import/">RP3</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
{% if rower.sporttrackstoken is not None and rower.sporttrackstoken != '' %}
|
||||
SportTracks,
|
||||
{% endif %}
|
||||
{% if rower.underarmourtoken is not None and rower.underarmourtoken != '' %}
|
||||
Under Armour (MapMyFitness),
|
||||
{% endif %}
|
||||
{% if rower.tptoken is not None and rower.tptoken != '' %}
|
||||
TrainingPeaks,
|
||||
{% endif %}
|
||||
@@ -32,9 +29,6 @@
|
||||
{% if rower.stravatoken is not None and rower.stravatoken != '' %}
|
||||
Strava,
|
||||
{% endif %}
|
||||
{% if rower.runkeepertoken is not None and rower.runkeepertoken != '' %}
|
||||
Runkeeper,
|
||||
{% endif %}
|
||||
{% if rower.rp3token is not None and rower.rp3token != '' %}
|
||||
RP3
|
||||
{% endif %}
|
||||
@@ -79,8 +73,6 @@
|
||||
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
|
||||
<p><a href="/rowers/me/nkauthorize/"><img src="/static/img/NKLiNKLogbook.png" alt="connect with NK Logbook" width="120"></a></p>
|
||||
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
|
||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
|
||||
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
|
||||
<p><a href="/rowers/me/polarauthorize/"><img src="/static/img/Polar_connectwith_btn_white.png"
|
||||
alt="connect with Polar" width="130"></a></p>
|
||||
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png"
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{% extends "newbase.html" %}
|
||||
{% load static %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Workouts{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h1>Available on MapMyFitness (UnderArmour)</h1>
|
||||
{% if workouts %}
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Import </th>
|
||||
<th> Date/Time </th>
|
||||
<th> Duration </th>
|
||||
<th> Total Distance</th>
|
||||
<th> Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for workout in workouts %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/rowers/workout/underarmourimport/{{ workout|ualookup:'id' }}/">Import</a></td>
|
||||
<td>{{ workout|ualookup:'starttime' }}</td>
|
||||
<td>{{ workout|ualookup:'duration' }} </td>
|
||||
<td>{{ workout|ualookup:'distance' }} m</td>
|
||||
<td>{{ workout|ualookup:'type' }}</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p> No workouts found. We only list workouts with time data series. </p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% include 'menu_workouts.html' %}
|
||||
{% endblock %}
|
||||
@@ -19,9 +19,8 @@ from rowers.plannedsessions import (
|
||||
race_can_register, race_can_submit,race_rower_status
|
||||
)
|
||||
|
||||
from rowers import c2stuff, runkeeperstuff
|
||||
from rowers import c2stuff
|
||||
from rowers.c2stuff import c2_open
|
||||
from rowers.runkeeperstuff import runkeeper_open
|
||||
from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispromember
|
||||
from rowers.mytypes import (
|
||||
otwtypes,adaptivetypes,sexcategories,weightcategories,workouttypes,
|
||||
@@ -434,7 +433,7 @@ def deltatimeprint(d): # pragma: no cover
|
||||
return strfdeltah(d)
|
||||
|
||||
@register.filter
|
||||
def c2userid(user):
|
||||
def c2userid(user): # pragma: no cover
|
||||
try:
|
||||
thetoken = c2_open(user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
@@ -453,16 +452,6 @@ def currency(word):
|
||||
|
||||
return '{amount:.2f}'.format(amount=amount)
|
||||
|
||||
@register.filter
|
||||
def rkuserid(user):
|
||||
try:
|
||||
thetoken = runkeeper_open(user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
return 0
|
||||
|
||||
rkuserid = runkeeperstuff.get_userid(thetoken)
|
||||
|
||||
return rkuserid
|
||||
|
||||
@register.filter
|
||||
def courselength(course): # pragma: no cover
|
||||
@@ -495,21 +484,6 @@ def lookuplong(dict, key):
|
||||
|
||||
return s
|
||||
|
||||
@register.filter
|
||||
def ualookup(dict, key):
|
||||
s = dict.get(key)
|
||||
|
||||
if key=='distance':
|
||||
s = int(float(s))
|
||||
|
||||
if key=='duration':
|
||||
s = secondstotimestring(int(s))
|
||||
|
||||
|
||||
if key=='starttime':
|
||||
s = dateutil.parser.parse(s)
|
||||
|
||||
return s
|
||||
|
||||
from rowers.models import PlannedSession
|
||||
@register.filter
|
||||
|
||||
+11
-1
@@ -334,9 +334,19 @@ def mocked_getsmallrowdata_db(*args, **kwargs):
|
||||
|
||||
return df
|
||||
|
||||
def mocked_getsmallrowdata_db_updatecp(*args, **kwargs):
|
||||
df = pd.read_csv('rowers/tests/testdata/colsfromdb.csv')
|
||||
|
||||
return df
|
||||
|
||||
def mocked_getsmallrowdata_db_setcp(*args, **kwargs):
|
||||
df = pd.read_csv('rowers/tests/testdata/colsfromdb2.csv')
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def mocked_getsmallrowdata_db_water(*args, **kwargs):
|
||||
df = pd.read_csv('rowers/tests/testdata/colsfromdb.csv')
|
||||
df = pd.read_csv('rowers/tests/testdata/colsfromdb3.csv')
|
||||
|
||||
return df
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ from mock import Mock, patch
|
||||
import pandas as pd
|
||||
import rowers.c2stuff as c2stuff
|
||||
import rowers.sporttracksstuff as sporttracksstuff
|
||||
import rowers.runkeeperstuff as runkeeperstuff
|
||||
|
||||
from django.urls import reverse, reverse_lazy
|
||||
|
||||
|
||||
@@ -462,22 +462,6 @@ class AsyncTaskTests(TestCase):
|
||||
res = tasks.handle_sporttracks_sync(1,url,headers,json.dumps(data,default=sporttracksstuff.default))
|
||||
self.assertEqual(res,1)
|
||||
|
||||
@patch('rowers.runkeeperstuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.runkeeperstuff.requests.get', side_effect=mocked_requests)
|
||||
def test_runkeeper_sync(self, mock_get, mock_post):
|
||||
authorizationstring = str('Bearer aap')
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json',
|
||||
'Content-Length':'nnn'}
|
||||
|
||||
url = "https://api.runkeeper.com/fitnessActivities"
|
||||
|
||||
data = runkeeperstuff.createrunkeeperworkoutdata(self.wwater)
|
||||
|
||||
|
||||
res = tasks.handle_runkeeper_sync(1,url,headers,json.dumps(data,default=sporttracksstuff.default))
|
||||
self.assertEqual(res,1)
|
||||
|
||||
@patch('rowers.c2stuff.requests.post',side_effect=mocked_requests)
|
||||
@patch('rowers.c2stuff.requests.get',side_effect=mocked_requests)
|
||||
@@ -519,14 +503,14 @@ class AsyncTaskTests(TestCase):
|
||||
|
||||
|
||||
@patch('rowers.dataprepnodjango.getsmallrowdata_db')
|
||||
def test_handle_updatecp(self,mocked_getsmallrowdata_db):
|
||||
def test_handle_updatecp(self,mocked_getsmallrowdata_db_updatecp):
|
||||
rower_id = 1
|
||||
workoutids = [1]
|
||||
res = tasks.handle_updatecp(rower_id,workoutids)
|
||||
self.assertEqual(res,1)
|
||||
|
||||
@patch('rowers.dataprepnodjango.getsmallrowdata_db')
|
||||
def test_handle_setcp(self,mocked_getsmallrowdata_db):
|
||||
def test_handle_setcp(self,mocked_getsmallrowdata_db_db_setcp):
|
||||
strokesdf = pd.read_csv('rowers/tests/testdata/uhfull.csv')
|
||||
filename = 'rowers/tests/testdata/temp/pq.gz'
|
||||
workoutids = 1
|
||||
|
||||
+41
-249
@@ -19,6 +19,8 @@ from rowers import stravastuff
|
||||
import urllib
|
||||
import json
|
||||
|
||||
import rowers.utils as utils
|
||||
|
||||
from django.db import transaction
|
||||
import rowers.garmin_stuff as gs
|
||||
|
||||
@@ -313,8 +315,10 @@ class C2Objects(DjangoTestCase):
|
||||
|
||||
response = self.c.get('/rowers/workout/c2import/12/',follow=True)
|
||||
|
||||
expected_url = reverse('workout_c2import_view')
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded2+'/edit/',
|
||||
expected_url=expected_url,
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -324,15 +328,18 @@ class C2Objects(DjangoTestCase):
|
||||
def test_c2_import_tz(self, mock_get, mocked_sqlalchemy):
|
||||
|
||||
response = self.c.get('/rowers/workout/c2import/22/',follow=True)
|
||||
expected_url = '/rowers/workout/c2list/'
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded2+'/edit/',
|
||||
expected_url=expected_url,
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
w = Workout.objects.get(id=2)
|
||||
self.assertEqual(w.timezone,'Europe/Prague')
|
||||
with open('rowers/tests/testdata/c2_timezone.json','r') as infile:
|
||||
data = json.load(infile)
|
||||
timezone = str(utils.get_timezone_from_c2data(data['data']))
|
||||
self.assertEqual(timezone,'Europe/Prague')
|
||||
|
||||
|
||||
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
|
||||
@@ -340,68 +347,46 @@ class C2Objects(DjangoTestCase):
|
||||
def test_c2_import_tz3(self, mock_get, mocked_sqlalchemy):
|
||||
|
||||
response = self.c.get('/rowers/workout/c2import/32/',follow=True)
|
||||
expected_url = '/rowers/workout/c2list/'
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded2+'/edit/',
|
||||
expected_url=expected_url,
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
w = Workout.objects.get(id=2)
|
||||
self.assertEqual(w.timezone,'UTC')
|
||||
with open('rowers/tests/testdata/c2_timezonebad.json','r') as infile:
|
||||
data = json.load(infile)
|
||||
|
||||
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
|
||||
timezone = str(utils.get_timezone_from_c2data(data['data']))
|
||||
self.assertEqual(timezone,'UTC')
|
||||
|
||||
@patch('rowers.tasks.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.dataprep.create_engine')
|
||||
def test_c2_import_tz2(self, mock_get, mocked_sqlalchemy):
|
||||
|
||||
response = self.c.get('/rowers/workout/c2import/31/',follow=True)
|
||||
expected_url = '/rowers/workout/c2list/'
|
||||
|
||||
result = tasks.handle_c2_getworkout(self.r.user.id,self.r.c2token,31,self.r.defaulttimezone)
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded2+'/edit/',
|
||||
expected_url=expected_url,
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
w = Workout.objects.get(id=2)
|
||||
self.assertEqual(w.timezone,'Europe/Amsterdam')
|
||||
with open('rowers/tests/testdata/c2_timezone2.json','r') as infile:
|
||||
data = json.load(infile)
|
||||
|
||||
self.assertEqual(w.starttime.strftime("%H:%M:%S"),"20:04:56")
|
||||
timezone = str(utils.get_timezone_from_c2data(data['data']))
|
||||
self.assertEqual(timezone,'Europe/Amsterdam')
|
||||
|
||||
startdatetime,starttime,workoutdate,duration,starttimeunix,timezone = utils.get_startdatetime_from_c2data(data['data'])
|
||||
self.assertEqual(starttime,"20:04:55")
|
||||
|
||||
|
||||
|
||||
@patch('rowers.dataprep.create_engine')
|
||||
def test_strokedata(self, mocked_sqlalchemy):
|
||||
with open('rowers/tests/testdata/c2stroketestdata.txt','r') as infile:
|
||||
res = json.load(infile)
|
||||
|
||||
strokedata = pd.DataFrame.from_dict(res['data'])
|
||||
|
||||
with open('rowers/tests/testdata/c2testdata.txt','r') as infile:
|
||||
res = json.load(infile)
|
||||
|
||||
data = res['data']
|
||||
|
||||
from rowers.views import add_workout_from_strokedata
|
||||
|
||||
res = add_workout_from_strokedata(self.u,1,data,strokedata,source='c2')
|
||||
|
||||
@patch('rowers.dataprep.create_engine')
|
||||
def test_strokedatanohr(self, mocked_sqlalchemy):
|
||||
with open('rowers/tests/testdata/c2strokedatanohr.txt','r') as infile:
|
||||
res = json.load(infile)
|
||||
|
||||
strokedata = pd.DataFrame.from_dict(res['data'])
|
||||
|
||||
with open('rowers/tests/testdata/c2testdata.txt','r') as infile:
|
||||
res = json.load(infile)
|
||||
|
||||
data = res['data']
|
||||
|
||||
from rowers.views import add_workout_from_strokedata
|
||||
|
||||
|
||||
res = add_workout_from_strokedata(self.u,1,data,strokedata,source='c2')
|
||||
|
||||
@patch('rowers.tasks.requests.get',side_effect=mocked_requests)
|
||||
@patch('rowers.tasks.requests.post',side_effect=mocked_requests)
|
||||
@patch('rowers.tasks.requests.session', side_effect=mocked_requests)
|
||||
@@ -485,13 +470,10 @@ class C2ObjectsTokenExpired(DjangoTestCase):
|
||||
@patch('rowers.dataprep.create_engine')
|
||||
def test_c2_import(self, mock_get, mocked_sqlalchemy):
|
||||
|
||||
response = self.c.get('/rowers/workout/c2import/12/',follow=True)
|
||||
response = self.c.get('/rowers/workout/c2import/12/')
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/list-workouts/',
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
#@pytest.mark.django_db
|
||||
@override_settings(TESTING=True)
|
||||
@@ -605,8 +587,10 @@ class NKObjects(DjangoTestCase):
|
||||
result = rowers.nkstuff.rower_nk_token_refresh(self.u)
|
||||
response = self.c.get('/rowers/workout/nkimport/469',follow=True)
|
||||
|
||||
expected_url = reverse('workout_nkimport_view')
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded1+'/edit/',
|
||||
expected_url=expected_url,
|
||||
status_code=301,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -626,8 +610,10 @@ class NKObjects(DjangoTestCase):
|
||||
result = rowers.nkstuff.rower_nk_token_refresh(self.u)
|
||||
response = self.c.get('/rowers/workout/nkimport/404',follow=True)
|
||||
|
||||
expected_url = reverse('workout_nkimport_view')
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded1+'/edit/',
|
||||
expected_url=expected_url,
|
||||
status_code=301,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -889,9 +875,10 @@ class StravaObjects(DjangoTestCase):
|
||||
mocked_getsmallrowdata_db):
|
||||
|
||||
response = self.c.get('/rowers/workout/stravaimport/12',follow=True)
|
||||
expected_url = reverse('workout_stravaimport_view')
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded2+'/edit/',
|
||||
expected_url=expected_url,
|
||||
status_code=301,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -1014,8 +1001,7 @@ class STObjects(DjangoTestCase):
|
||||
|
||||
response = self.c.get('/rowers/workout/sporttracksimport/13/',follow=True)
|
||||
|
||||
expected_url = reverse('workout_edit_view',
|
||||
kwargs = {'id':encoder.encode_hex(2)})
|
||||
expected_url = '/rowers/workout/sporttracksimport/'
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url=expected_url,
|
||||
@@ -1056,200 +1042,6 @@ class STObjects(DjangoTestCase):
|
||||
|
||||
res = add_workout_from_data(self.u,1,data,data)
|
||||
|
||||
#@pytest.mark.django_db
|
||||
@override_settings(TESTING=True)
|
||||
class RunKeeperObjects(DjangoTestCase):
|
||||
def setUp(self):
|
||||
self.c = Client()
|
||||
self.u = User.objects.create_user('john',
|
||||
'sander@ds.ds',
|
||||
'koeinsloot')
|
||||
|
||||
self.u.first_name = 'John'
|
||||
self.u.last_name = 'Sander'
|
||||
self.u.save()
|
||||
self.r = Rower.objects.create(user=self.u,gdproptin=True,surveydone=True,
|
||||
gdproptindate=timezone.now()
|
||||
)
|
||||
|
||||
|
||||
self.r.runkeepertoken = '12'
|
||||
|
||||
self.r.save()
|
||||
|
||||
self.c.login(username='john',password='koeinsloot')
|
||||
|
||||
self.nu = datetime.datetime.now()
|
||||
|
||||
filename = 'rowers/tests/testdata/testdata.csv'
|
||||
|
||||
rr = rrower(hrmax=self.r.max,hrut2=self.r.ut2,
|
||||
hrut1=self.r.ut1,hrat=self.r.at,
|
||||
hrtr=self.r.tr,hran=self.r.an,ftp=self.r.ftp)
|
||||
row = rdata(csvfile=filename,rower=rr)
|
||||
totaldist = row.df['cum_dist'].max()
|
||||
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
|
||||
totaltime = totaltime+row.df.loc[:,' ElapsedTime (sec)'].iloc[0]
|
||||
|
||||
|
||||
hours = int(totaltime/3600.)
|
||||
minutes = int((totaltime - 3600.*hours)/60.)
|
||||
seconds = int(totaltime - 3600.*hours - 60.*minutes)
|
||||
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
|
||||
|
||||
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
|
||||
|
||||
|
||||
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
|
||||
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
|
||||
|
||||
self.w = Workout.objects.create(
|
||||
name='testworkout',workouttype='water',
|
||||
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
|
||||
starttime=workoutstarttime,
|
||||
startdatetime=row.rowdatetime,
|
||||
duration=duration,distance=totaldist,
|
||||
csvfilename=filename
|
||||
)
|
||||
|
||||
@patch('rowers.runkeeperstuff.requests.post', side_effect=mocked_requests)
|
||||
def test_runkeeper_callback(self, mock_post):
|
||||
response = self.c.get('/runkeeper_callback?code=dsdoij232s',follow=True)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@patch('rowers.runkeeperstuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.runkeeperstuff.requests.get', side_effect=mocked_requests)
|
||||
def test_runkeeper_upload(self, mock_get, mock_post):
|
||||
response = self.c.get('/rowers/workout/'+encoded1+'/runkeeperuploadw/')
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url = '/rowers/workout/'+encoded1+'/edit/',
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
@patch('rowers.runkeeperstuff.requests.get', side_effect=mocked_requests)
|
||||
def test_runkeeper_list(self, mock_get):
|
||||
response = self.c.get('/rowers/workout/runkeeperimport',follow=True)
|
||||
|
||||
self.assertEqual(response.status_code,200)
|
||||
|
||||
@patch('rowers.imports.requests.get', side_effect=mocked_requests)
|
||||
def test_runkeeper_import(self, mock_get):
|
||||
|
||||
response = self.c.get('/rowers/workout/runkeeperimport/12/',follow=True)
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded2+'/edit/',
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(TESTING=True)
|
||||
class UAObjects(DjangoTestCase):
|
||||
def setUp(self):
|
||||
self.c = Client()
|
||||
self.u = User.objects.create_user('john',
|
||||
'sander@ds.ds',
|
||||
'koeinsloot')
|
||||
|
||||
self.u.first_name = 'John'
|
||||
self.u.last_name = 'Sander'
|
||||
self.u.save()
|
||||
self.r = Rower.objects.create(user=self.u,gdproptin=True,surveydone=True,
|
||||
gdproptindate=timezone.now()
|
||||
)
|
||||
|
||||
|
||||
self.r.underarmourtoken = '12'
|
||||
self.r.underarmourrefreshtoken = '12'
|
||||
self.r.underarmourtokenexpirydate = arrow.get(datetime.datetime.now()+datetime.timedelta(days=1)).datetime
|
||||
self.r.save()
|
||||
|
||||
self.c.login(username='john',password='koeinsloot')
|
||||
|
||||
self.nu = datetime.datetime.now()
|
||||
|
||||
filename = 'rowers/tests/testdata/testdata.csv'
|
||||
|
||||
rr = rrower(hrmax=self.r.max,hrut2=self.r.ut2,
|
||||
hrut1=self.r.ut1,hrat=self.r.at,
|
||||
hrtr=self.r.tr,hran=self.r.an,ftp=self.r.ftp)
|
||||
row = rdata(csvfile=filename,rower=rr)
|
||||
totaldist = row.df['cum_dist'].max()
|
||||
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
|
||||
totaltime = totaltime+row.df.loc[:,' ElapsedTime (sec)'].iloc[0]
|
||||
|
||||
|
||||
hours = int(totaltime/3600.)
|
||||
minutes = int((totaltime - 3600.*hours)/60.)
|
||||
seconds = int(totaltime - 3600.*hours - 60.*minutes)
|
||||
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
|
||||
|
||||
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
|
||||
|
||||
|
||||
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
|
||||
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
|
||||
|
||||
self.w = Workout.objects.create(
|
||||
name='testworkout',workouttype='water',
|
||||
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
|
||||
starttime=workoutstarttime,
|
||||
startdatetime=row.rowdatetime,
|
||||
duration=duration,distance=totaldist,
|
||||
csvfilename=filename
|
||||
)
|
||||
|
||||
@patch('rowers.imports.requests.post', side_effect=mocked_requests)
|
||||
def test_underarmour_callback(self, mock_post):
|
||||
response = self.c.get('/underarmour_callback?code=dsdoij232s',follow=True)
|
||||
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
@patch('rowers.underarmourstuff.requests.post', side_effect=mocked_requests)
|
||||
def test_underarmour_token_refresh(self, mock_post):
|
||||
response = self.c.get('/rowers/me/underarmourrefresh/',follow=True)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
@patch('rowers.underarmourstuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.underarmourstuff.requests.get', side_effect=mocked_requests)
|
||||
def test_underarmour_upload(self, mock_get, mock_post):
|
||||
response = self.c.get('/rowers/workout/'+encoded1+'/underarmouruploadw/')
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url = '/rowers/workout/'+encoded1+'/edit/',
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
@patch('rowers.underarmourstuff.requests.get', side_effect=mocked_requests)
|
||||
def test_underarmour_list(self, mock_get):
|
||||
response = self.c.get('/rowers/workout/underarmourimport',follow=True)
|
||||
|
||||
self.assertEqual(response.status_code,200)
|
||||
|
||||
@patch('rowers.imports.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.dataprep.create_engine')
|
||||
def test_underarmour_import(self, mock_get, mocked_sqlalchemy):
|
||||
|
||||
response = self.c.get('/rowers/workout/underarmourimport/12/',follow=True)
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded2+'/edit/',
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
#@pytest.mark.django_db
|
||||
|
||||
+1399
File diff suppressed because it is too large
Load Diff
+1399
File diff suppressed because it is too large
Load Diff
@@ -94,14 +94,10 @@
|
||||
104,127,workout_sporttracksimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
105,129,workout_getsporttracksworkout_all,gets all C2 workouts (now redirects due to NoTokenError,TRUE,302,basic,302,302,FALSE,302,302,FALSE,302,302,FALSE,FALSE,FALSE,TRUE,TRUE,
|
||||
106,130,workout_polarimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
107,132,workout_runkeeperimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
108,134,workout_underarmourimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
109,135,workout_c2_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
|
||||
110,136,workout_strava_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
|
||||
111,137,workout_recalcsummary_view,recalculates workout summary,TRUE,403,basic,302,403,basic,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
|
||||
112,138,workout_sporttracks_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
|
||||
113,139,workout_runkeeper_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
|
||||
114,140,workout_underarmour_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
|
||||
115,141,workout_tp_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
|
||||
116,142,multi_compare_view,compare workouts ,TRUE,302,basic,302,403,basic,302,403,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE,
|
||||
117,145,alerts_view,view alerts,TRUE,302,basic,200,403,basic,403,403,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
@@ -151,11 +147,8 @@
|
||||
165,210,rower_revokeapp_view,revoke app,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
166,211,rower_strava_authorize,authorize on strava,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
167,212,rower_sporttracks_authorize,authorize on sporttracks,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
168,213,rower_underarmour_authorize,authorize on UA,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
169,214,rower_tp_authorize,authorize on TP,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
170,215,rower_runkeeper_authorize,authorize on RK,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
171,216,rower_sporttracks_token_refresh,refresh token,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
172,217,rower_underarmour_token_refresh,refresh token,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
173,218,rower_tp_token_refresh,refresh token,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
174,219,rower_c2_token_refresh,refresh token,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
175,220,rower_favoritecharts_view,See favorite charts,TRUE,302,pro,200,302,pro,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
@@ -232,9 +225,7 @@
|
||||
247,343,rower_process_callback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
248,344,rower_process_stravacallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
249,345,rower_process_sporttrackscallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
250,346,rower_process_underarmourcallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
251,347,rower_process_polarcallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
252,348,rower_process_runkeepercallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
253,349,rower_process_tpcallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
254,350,rower_process_twittercallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
255,351,set_language,Set language,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
|
||||
|
@@ -99,8 +99,6 @@ class TraverseLinksTest(TestCase):
|
||||
'.*authorize.*',
|
||||
'.*youtu.*',
|
||||
'.*earth.*',
|
||||
'.*underarmour.*',
|
||||
'.*runkeeper.*',
|
||||
'.*c2list.*',
|
||||
'.*stravaimport.*',
|
||||
'.*performancephones.*',
|
||||
|
||||
@@ -1,544 +0,0 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
from rowers.imports import *
|
||||
|
||||
import numpy
|
||||
|
||||
import rowers.mytypes as mytypes
|
||||
from rowers.mytypes import otwtypes
|
||||
from rowers.rower_rules import is_workout_user
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
UNDERARMOUR_CLIENT_KEY,
|
||||
UNDERARMOUR_CLIENT_SECRET,
|
||||
UNDERARMOUR_REDIRECT_URI,
|
||||
)
|
||||
|
||||
oauth_data = {
|
||||
'client_id': UNDERARMOUR_CLIENT_KEY,
|
||||
'client_secret': UNDERARMOUR_CLIENT_SECRET,
|
||||
'redirect_uri': UNDERARMOUR_REDIRECT_URI,
|
||||
'autorization_uri': "https://www.mapmyfitness.com/v7.1/oauth2/uacf/authorize/",
|
||||
'content_type': 'application/x-www-form-urlencoded',
|
||||
'tokenname': 'underarmourtoken',
|
||||
'refreshtokenname': 'underarmourrefreshtoken',
|
||||
'expirydatename': 'underarmourtokenexpirydate',
|
||||
'bearer_auth': True,
|
||||
'base_url': "https://api.ua.com/v7.1/oauth2/access_token/",
|
||||
'scope':'write',
|
||||
}
|
||||
|
||||
# Checks if user has UnderArmour token, renews them if they are expired
|
||||
def underarmour_open(user):
|
||||
return imports_open(user,oauth_data)
|
||||
|
||||
# Refresh ST token using refresh token
|
||||
def do_refresh_token(refreshtoken,access_token):
|
||||
return imports_do_refresh_token(
|
||||
refreshtoken,oauth_data,access_token=access_token
|
||||
)
|
||||
|
||||
# Exchange access code for long-lived access token
|
||||
def get_token(code):
|
||||
return imports_get_token(code,oauth_data)
|
||||
|
||||
# Make authorization URL including random string
|
||||
def make_authorization_url(request):
|
||||
return imports_make_authorization_url(oauth_data) # pragma: no cover
|
||||
|
||||
# Get list of workouts available on Underarmour
|
||||
def get_underarmour_workout_list(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s)
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/workout/?user="+str(get_userid(r.underarmourtoken))+"&order_by=-start_datetime"
|
||||
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
|
||||
return s
|
||||
|
||||
# Get workout summary data by Underarmour ID
|
||||
def get_workout(user,underarmourid,do_async=False):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.underarmourtoken == '') or (r.underarmourtoken is None): # pragma: no cover
|
||||
return custom_exception_handler(401,s)
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/workout/"+str(underarmourid)+"/?field_set=time_series"
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
data = s.json()
|
||||
|
||||
strokedata = pd.DataFrame.from_dict({
|
||||
key: pd.Series(value) for key, value in data.items()
|
||||
})
|
||||
|
||||
return data,strokedata
|
||||
|
||||
|
||||
# Create Workout Data for upload to Underarmour
|
||||
def createunderarmourworkoutdata(w):
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except: # pragma: no cover
|
||||
return 0
|
||||
|
||||
st = w.startdatetime.astimezone(pytz.timezone(w.timezone))
|
||||
start_time = st.isoformat()
|
||||
|
||||
averagehr = int(row.df[' HRCur (bpm)'].mean())
|
||||
minhr = int(row.df[' HRCur (bpm)'].min())
|
||||
maxhr = int(row.df[' HRCur (bpm)'].max())
|
||||
averagespm = int(row.df[' Cadence (stokes/min)'].mean()/2.)
|
||||
minspm = int(row.df[' Cadence (stokes/min)'].min()/2.)
|
||||
maxspm = int(row.df[' Cadence (stokes/min)'].max()/2.)
|
||||
maxhr = int(row.df[' HRCur (bpm)'].max())
|
||||
duration = w.duration.hour*3600
|
||||
duration += w.duration.minute*60
|
||||
duration += w.duration.second
|
||||
duration += +1.0e-6*w.duration.microsecond
|
||||
name = w.name
|
||||
try:
|
||||
notes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
|
||||
except TypeError:
|
||||
notes = 'from '+w.workoutsource+' via rowsandall.com'
|
||||
|
||||
# adding diff, trying to see if this is valid
|
||||
#t = row.df.loc[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)']
|
||||
t = row.df.loc[:,'TimeStamp (sec)'].values #-row.df.ix[0,'TimeStamp (sec)']
|
||||
|
||||
# t += arrow.get(st).timestamp()
|
||||
|
||||
# t[0] = t[1]
|
||||
|
||||
d = row.df.loc[:,'cum_dist'].values
|
||||
d[0] = d[1]
|
||||
t = t.astype(float).tolist()
|
||||
|
||||
|
||||
|
||||
|
||||
d = d.astype(int).tolist()
|
||||
spm = row.df[' Cadence (stokes/min)'].astype(int).tolist()
|
||||
spm[0] = spm[1]
|
||||
hr = row.df[' HRCur (bpm)'].astype(int).tolist()
|
||||
speed = row.df[' AverageBoatSpeed (m/s)']
|
||||
speedmin = float(row.df[' AverageBoatSpeed (m/s)'].min())
|
||||
speedmax = float(row.df[' AverageBoatSpeed (m/s)'].max())
|
||||
speedmean = float(row.df[' AverageBoatSpeed (m/s)'].mean())
|
||||
speed = speed.replace(np.inf,0).tolist()
|
||||
|
||||
|
||||
|
||||
haslatlon=1
|
||||
|
||||
try: # pragma: no cover
|
||||
lat = row.df[' latitude']
|
||||
lon = row.df[' longitude']
|
||||
if not lat.std() and not lon.std():
|
||||
haslatlon = 0
|
||||
except KeyError:
|
||||
haslatlon = 0
|
||||
|
||||
|
||||
# path data
|
||||
if haslatlon: # pragma: no cover
|
||||
locdata = []
|
||||
for e in zip(t,lat.values,lon.values):
|
||||
point = {
|
||||
'lat':e[1],
|
||||
'lng':e[2],
|
||||
'elevation':0,
|
||||
}
|
||||
locdata.append([e[0],point])
|
||||
|
||||
hrdata = []
|
||||
for e in zip(t,hr):
|
||||
point = [e[0],
|
||||
e[1]
|
||||
]
|
||||
hrdata.append(point)
|
||||
|
||||
distancedata = []
|
||||
for e in zip(t,d):
|
||||
point = [e[0],
|
||||
e[1]
|
||||
]
|
||||
distancedata.append(point)
|
||||
|
||||
spmdata = []
|
||||
for e in zip(t,spm):
|
||||
spmdata.append([e[0],e[1]])
|
||||
|
||||
|
||||
timeseries = {
|
||||
"distance": distancedata,
|
||||
"heartrate": hrdata,
|
||||
"cadence": spmdata,
|
||||
}
|
||||
|
||||
|
||||
|
||||
aggregates = {
|
||||
"elapsed_time_total": int(duration),
|
||||
"active_time_total": int(duration),
|
||||
"distance_total": int(max(d)),
|
||||
"heartrate_avg": averagehr,
|
||||
"heart_rate_min": minhr,
|
||||
"heart_rate_max": maxhr,
|
||||
"speed_min": speedmin,
|
||||
"speed_max": speedmax,
|
||||
"speed_avg": speedmean,
|
||||
"cadence_min": minspm,
|
||||
"cadence_max": maxspm,
|
||||
"cadence_avg": averagespm,
|
||||
}
|
||||
|
||||
|
||||
if haslatlon: # pragma: no cover
|
||||
timeseries["position"] = locdata
|
||||
|
||||
data = {
|
||||
"start_datetime": start_time,
|
||||
"name": name,
|
||||
"has_time_series": True,
|
||||
"time_series": timeseries,
|
||||
"aggregates": aggregates,
|
||||
"start_locale_timezone": "Etc/UTC",
|
||||
"activity_type": "/v7.1/activity_type/128/",
|
||||
"notes": notes,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
# Obtain Underarmour Workout ID and activity type
|
||||
def get_idfromuri(user,links):
|
||||
id = links['self'][0]['id']
|
||||
typeid = links['activity_type'][0]['id']
|
||||
|
||||
typename = get_typefromid(typeid,user)
|
||||
|
||||
return id,typename
|
||||
|
||||
def getidfromresponse(response):
|
||||
t = response.json()
|
||||
|
||||
links = t["_links"]
|
||||
|
||||
id = links["self"][0]["id"]
|
||||
|
||||
return int(id)
|
||||
|
||||
def refresh_ua_actlist(user): # pragma: no cover
|
||||
r = Rower.objects.get(user=user)
|
||||
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/activity_type/"
|
||||
response = requests.get(url,headers=headers)
|
||||
|
||||
me_json = response.json()
|
||||
types = me_json["_embedded"]["activity_types"]
|
||||
w = {int(t["_links"]["self"][0]["id"]):t["name"] for t in types}
|
||||
wdf = pd.Series(w,name='Name')
|
||||
wdf.to_csv('static/rigging/ua2.csv',index_label='id',header=True)
|
||||
return w
|
||||
|
||||
try:
|
||||
activities = pd.read_csv('static/rigging/ua2.csv',index_col='id')
|
||||
actdict = activities.to_dict()['Name']
|
||||
except: # pragma: no cover
|
||||
actdict = {}
|
||||
|
||||
|
||||
def get_typefromid(typeid,user):
|
||||
r = Rower.objects.get(user=user)
|
||||
try:
|
||||
res = actdict[int(typeid)]
|
||||
except KeyError: # pragma: no cover
|
||||
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/activity_type/"+str(typeid)
|
||||
response = requests.get(url,headers=headers)
|
||||
|
||||
me_json = response.json()
|
||||
|
||||
try:
|
||||
res = me_json['name']
|
||||
except KeyError:
|
||||
res = 0
|
||||
|
||||
return res
|
||||
|
||||
|
||||
|
||||
# Get user id, having access token
|
||||
# Handy for checking if the API access is working
|
||||
def get_userid(access_token):
|
||||
authorizationstring = str('Bearer ' + access_token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
|
||||
url = "https://api.ua.com/v7.1/user/self/"
|
||||
response = requests.get(url,headers=headers)
|
||||
|
||||
me_json = response.json()
|
||||
|
||||
try:
|
||||
res = me_json['id']
|
||||
except KeyError: # pragma: no cover
|
||||
res = 0
|
||||
|
||||
return res
|
||||
|
||||
def default(o): # pragma: no cover
|
||||
if isinstance(o, numpy.int64): return int(o)
|
||||
raise TypeError
|
||||
|
||||
|
||||
def workout_ua_upload(user,w): # pragma: no cover
|
||||
message = "Uploading to MapMyFitness"
|
||||
uaid = 0
|
||||
|
||||
r = w.user
|
||||
|
||||
thetoken = underarmour_open(r.user)
|
||||
|
||||
# ready to upload. Hurray
|
||||
|
||||
if (is_workout_user(user,w)):
|
||||
data = createunderarmourworkoutdata(w)
|
||||
# return HttpResponse(json.dumps(data))
|
||||
if not data:
|
||||
message = "Data error"
|
||||
uaid = 0
|
||||
return message, uaid
|
||||
|
||||
authorizationstring = str('Bearer ' + thetoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
url = "https://api.ua.com/v7.1/workout/"
|
||||
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
|
||||
|
||||
# check for duplicate error first
|
||||
if (response.status_code == 409 ):
|
||||
message = "Duplicate error"
|
||||
w.uploadedtounderarmour = -1
|
||||
uaid = -1
|
||||
w.save()
|
||||
elif (response.status_code == 201 or response.status_code==200):
|
||||
uaid = getidfromresponse(response)
|
||||
w.uploadedtounderarmour = uaid
|
||||
w.save()
|
||||
return 'Successfully synchronized with MapMyFitness',uaid
|
||||
else:
|
||||
s = response
|
||||
message = "Something went wrong in workout_underarmour_upload_view: %s - %s" % (s.reason,s.text)
|
||||
uaid = 0
|
||||
return message, uaid
|
||||
|
||||
else:
|
||||
message = "You are not authorized to upload this workout"
|
||||
uaid = 0
|
||||
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: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
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: # pragma: no cover
|
||||
title = "Imported data"
|
||||
|
||||
timeseries = data['time_series']
|
||||
|
||||
# position, distance, speed, cadence, power,
|
||||
|
||||
try:
|
||||
res = splituadata(timeseries['distance'])
|
||||
distance = res[1]
|
||||
times_distance = res[0]
|
||||
except KeyError: # pragma: no cover
|
||||
message = "Error. No distance data"
|
||||
return (0,message)
|
||||
|
||||
|
||||
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: # pragma: no cover
|
||||
times_location = times_distance
|
||||
latcoord = np.zeros(len(times_distance))
|
||||
loncoord = np.zeros(len(times_distance))
|
||||
if workouttype in otwtypes:
|
||||
workouttype = 'rower'
|
||||
|
||||
try:
|
||||
res = splituadata(timeseries['cadence'])
|
||||
times_spm = res[0]
|
||||
spm = res[1]
|
||||
except KeyError: # pragma: no cover
|
||||
times_spm = times_distance
|
||||
spm = 0*times_distance
|
||||
|
||||
try:
|
||||
res = splituadata(timeseries['heartrate'])
|
||||
hr = res[1]
|
||||
times_hr = res[0]
|
||||
except KeyError: # pragma: no cover
|
||||
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',
|
||||
dosmooth=r.dosmooth,
|
||||
title=title,
|
||||
notes=comments)
|
||||
|
||||
return (id,message)
|
||||
+1
-27
@@ -124,8 +124,6 @@ def matchsync(line):
|
||||
tester3 = tester+'(.*)((tp)|(trainingpeaks))'
|
||||
tester4 = tester+'(.*)(strava)'
|
||||
tester5 = tester+'(.*)((st)|(sporttracks))'
|
||||
tester6 = tester+'(.*)((rk)|(runkeeper))'
|
||||
tester7 = tester+'(.*)((mapmyfitness)|(underarmour)|(ua))'
|
||||
|
||||
tester = re.compile(tester)
|
||||
|
||||
@@ -135,7 +133,6 @@ def matchsync(line):
|
||||
('upload_totp',re.compile(tester3)),
|
||||
('upload_to_Strava',re.compile(tester4)),
|
||||
('upload_to_SportTracks',re.compile(tester5)),
|
||||
('upload_to_RunKeeper',re.compile(tester6)),
|
||||
('upload_to_MapMyFitness',re.compile(tester7)),
|
||||
]
|
||||
for t in testers:
|
||||
@@ -260,10 +257,6 @@ def getsyncoptions(uploadoptions,values): # pragma: no cover
|
||||
uploadoptions['upload_to_Strava'] = True
|
||||
if v in ['st','sporttracks']:
|
||||
uploadoptions['upload_to_SportTracks'] = True
|
||||
if v in ['rk','runkeeper']:
|
||||
uploadoptions['upload_to_RunKeeper'] = True
|
||||
if v in ['ua','underarmour','mapmyfitness']:
|
||||
uploadoptions['upload_to_MapMyFitness'] = True
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -490,8 +483,7 @@ def make_plot(r,w,f1,f2,plottype,title,imagename='',plotnr=0):
|
||||
import rowers.c2stuff as c2stuff
|
||||
import rowers.stravastuff as stravastuff
|
||||
import rowers.sporttracksstuff as sporttracksstuff
|
||||
import rowers.runkeeperstuff as runkeeperstuff
|
||||
import rowers.underarmourstuff as underarmourstuff
|
||||
|
||||
import rowers.tpstuff as tpstuff
|
||||
|
||||
from rowers.rower_rules import is_promember
|
||||
@@ -644,24 +636,6 @@ def do_sync(w,options, quick=False):
|
||||
id = 0
|
||||
|
||||
|
||||
if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export): # pragma: no cover
|
||||
try:
|
||||
message,id = runkeeperstuff.workout_runkeeper_upload(
|
||||
w.user.user,w,asynchron=True,
|
||||
)
|
||||
except NoTokenError:
|
||||
message = "Please connect to Runkeeper first"
|
||||
id = 0
|
||||
|
||||
if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export): # pragma: no cover
|
||||
try:
|
||||
message,id = underarmourstuff.workout_ua_upload(
|
||||
w.user.user,w
|
||||
)
|
||||
except NoTokenError:
|
||||
message = "Please connect to MapMyFitness first"
|
||||
id = 0
|
||||
|
||||
|
||||
if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export): # pragma: no cover
|
||||
try:
|
||||
|
||||
@@ -508,16 +508,10 @@ urlpatterns = [
|
||||
re_path(r'^workout/sporttracksimport/all/$',views.workout_getsporttracksworkout_all,name='workout_getsporttracksworkout_all'),
|
||||
re_path(r'^workout/polarimport/$',views.workout_polarimport_view,name='workout_polarimport_view'),
|
||||
re_path(r'^workout/polarimport/user/(?P<userid>\d+)/',views.workout_polarimport_view,name='workout_polarimport_view'),
|
||||
re_path(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'),
|
||||
re_path(r'^workout/runkeeperimport/user/(?P<userid>\d+)/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'),
|
||||
re_path(r'^workout/underarmourimport/user/(?P<userid>\d+)/$',views.workout_underarmourimport_view,name='workout_underarmourimport_view'),
|
||||
re_path(r'^workout/underarmourimport/$',views.workout_underarmourimport_view,name='workout_underarmourimport_view'),
|
||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/c2uploadw/$',views.workout_c2_upload_view,name='workout_c2_upload_view'),
|
||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/stravauploadw/$',views.workout_strava_upload_view,name='workout_strava_upload_view'),
|
||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/recalcsummary/$',views.workout_recalcsummary_view,name='workout_recalcsummary_view'),
|
||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/sporttracksuploadw/$',views.workout_sporttracks_upload_view,name='workout_sporttracks_upload_view'),
|
||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/runkeeperuploadw/$',views.workout_runkeeper_upload_view,name='workout_runkeeper_upload_view'),
|
||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/underarmouruploadw/$',views.workout_underarmour_upload_view,name='workout_underarmour_upload_view'),
|
||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/tpuploadw/$',views.workout_tp_upload_view,name='workout_tp_upload_view'),
|
||||
re_path(r'^multi-compare/workout/(?P<id>\b[0-9A-Fa-f]+\b)/user/(?P<userid>\d+)/$',views.multi_compare_view,
|
||||
name='multi_compare_view'),
|
||||
@@ -600,12 +594,9 @@ urlpatterns = [
|
||||
re_path(r'^me/stravaauthorize/$',views.rower_strava_authorize,name='rower_strava_authorize'),
|
||||
re_path(r'^me/garminauthorize/$',views.rower_garmin_authorize,name='rower_garmin_authorize'),
|
||||
re_path(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize,name='rower_sporttracks_authorize'),
|
||||
re_path(r'^me/underarmourauthorize/$',views.rower_underarmour_authorize,name='rower_underarmour_authorize'),
|
||||
re_path(r'^me/tpauthorize/$',views.rower_tp_authorize,name='rower_tp_authorize'),
|
||||
re_path(r'^me/rp3authorize/$',views.rower_rp3_authorize,name='rower_rp3_authorize'),
|
||||
re_path(r'^me/runkeeperauthorize/$',views.rower_runkeeper_authorize,name='rower_runkeeper_authorize'),
|
||||
re_path(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh,name='rower_sporttracks_token_refresh'),
|
||||
re_path(r'^me/underarmourrefresh/$',views.rower_underarmour_token_refresh,name='rower_underarmoud_token_refresh'),
|
||||
re_path(r'^me/tprefresh/$',views.rower_tp_token_refresh,name='rower_tp_token_refresh'),
|
||||
re_path(r'^me/c2refresh/$',views.rower_c2_token_refresh,name='rower_c2_token_refresh'),
|
||||
re_path(r'^me/favoritecharts/$',views.rower_favoritecharts_view,name='rower_favoritecharts_view'),
|
||||
|
||||
+49
-1
@@ -15,7 +15,7 @@ import uuid
|
||||
import datetime
|
||||
|
||||
import json
|
||||
|
||||
import time
|
||||
from fitparse import FitFile
|
||||
from django.conf import settings
|
||||
|
||||
@@ -26,6 +26,11 @@ import requests
|
||||
from django.http import HttpResponse
|
||||
|
||||
import humanize
|
||||
from pytz.exceptions import UnknownTimeZoneError
|
||||
import pytz
|
||||
import iso8601
|
||||
from iso8601 import ParseError
|
||||
import arrow
|
||||
|
||||
lbstoN = 4.44822
|
||||
|
||||
@@ -105,6 +110,15 @@ info_calls = [
|
||||
|
||||
import random
|
||||
|
||||
def dologging(filename,s):
|
||||
tstamp = time.localtime()
|
||||
timestamp = time.strftime('%b-%d-%Y %H:%M:%S', tstamp)
|
||||
with open(filename,'a') as f:
|
||||
f.write('\n')
|
||||
f.write(timestamp)
|
||||
f.write(' ')
|
||||
f.write(s)
|
||||
|
||||
def to_pace(pace):
|
||||
minutes, seconds = divmod(pace,60)
|
||||
seconds, rest = divmod(seconds, 1)
|
||||
@@ -1223,3 +1237,37 @@ def intervals_to_string(vals, units, typ):
|
||||
s = s[1:]
|
||||
|
||||
return s
|
||||
|
||||
def get_timezone_from_c2data(data):
|
||||
|
||||
try:
|
||||
timezone = pytz.timezone(data['timezone'])
|
||||
except UnknownTimeZoneError:
|
||||
timezone = pytz.utc
|
||||
except KeyError: # pragma: no cover
|
||||
timezone = pytz.utc
|
||||
|
||||
return timezone
|
||||
|
||||
|
||||
def get_startdatetime_from_c2data(data):
|
||||
timezone = get_timezone_from_c2data(data)
|
||||
try:
|
||||
startdatetime = iso8601.parse_date(data['date_utc'])
|
||||
except: # pragma: no cover
|
||||
startdatetime = iso8601.parse_date(data['date'])
|
||||
|
||||
totaltime = data['time']/10.
|
||||
duration = totaltime_sec_to_string(totaltime)
|
||||
starttimeunix = arrow.get(startdatetime).timestamp()-totaltime
|
||||
startdatetime = arrow.get(starttimeunix)
|
||||
startdatetime = startdatetime.astimezone(timezone)
|
||||
|
||||
workoutdate = startdatetime.astimezone(
|
||||
timezone
|
||||
).strftime('%Y-%m-%d')
|
||||
starttime = startdatetime.astimezone(
|
||||
timezone
|
||||
).strftime('%H:%M:%S')
|
||||
|
||||
return startdatetime,starttime,workoutdate,duration,starttimeunix,timezone
|
||||
|
||||
+42
-670
@@ -81,113 +81,17 @@ def workout_tp_upload_view(request,id=0):
|
||||
def workout_strava_upload_view(request,id=0):
|
||||
message = ""
|
||||
r = getrower(request.user)
|
||||
res = -1
|
||||
w = get_workout_by_opaqueid(request,id)
|
||||
result = -1
|
||||
comment, result = stravastuff.workout_strava_upload(r.user,w,asynchron=True)
|
||||
messages.info(request,'Your workout will be synchronized to Strava in the background')
|
||||
|
||||
try:
|
||||
thetoken = strava_open(request.user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
|
||||
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
|
||||
else:
|
||||
# ready to upload. Hurray
|
||||
w = get_workout_by_opaqueid(request,id)
|
||||
r = w.user
|
||||
|
||||
try:
|
||||
tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w)
|
||||
if tcxfile:
|
||||
with open(tcxfile,'rb') as f:
|
||||
try:
|
||||
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
|
||||
except TypeError:
|
||||
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
|
||||
activity_type = 'Ride'
|
||||
if w.workouttype in mytypes.rowtypes:
|
||||
activity_type = r.stravaexportas
|
||||
if activity_type == 'match':
|
||||
try:
|
||||
activity_type = mytypes.stravamapping[w.workouttype]
|
||||
except KeyError: # pragma: no cover
|
||||
activity_type = 'Ride'
|
||||
else: # pragma: no cover
|
||||
try:
|
||||
activity_type = mytypes.stravamapping[w.workouttype]
|
||||
except KeyError:
|
||||
activity_type = 'Ride'
|
||||
|
||||
res,mes = stravastuff.handle_stravaexport(
|
||||
f,w.name,
|
||||
r.stravatoken,
|
||||
description=newnotes,
|
||||
activity_type=activity_type,quick=False)
|
||||
if res==0: # pragma: no cover
|
||||
messages.error(request,mes)
|
||||
w.uploadedtostrava = -1
|
||||
w.save()
|
||||
try:
|
||||
os.remove(tcxfile)
|
||||
except WindowsError:
|
||||
pass
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
})
|
||||
response = HttpResponseRedirect(url)
|
||||
return response
|
||||
|
||||
try:
|
||||
w.uploadedtostrava = res
|
||||
w.save()
|
||||
try:
|
||||
os.remove(tcxfile)
|
||||
except WindowsError: # pragma: no cover
|
||||
pass
|
||||
url = reverse('workout_edit_view',kwargs={'id':w.id})
|
||||
|
||||
|
||||
messages.info(request,mes)
|
||||
except: # pragma: no cover
|
||||
with open("media/stravaerrors.log","a") as errorlog:
|
||||
errorstring = str(sys.exc_info()[0])
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
errorlog.write(timestr+errorstring+"\r\n")
|
||||
errorlog.write("views.py line 826\r\n")
|
||||
message = 'Error: '+errorstring
|
||||
messages.error(request,message)
|
||||
else: # pragma: no cover # No tcxfile
|
||||
message = "Strava Data error "+tcxmessg
|
||||
messages.error(request,message)
|
||||
w.uploadedtostrava = -1
|
||||
w.save()
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
})
|
||||
response = HttpResponseRedirect(url)
|
||||
|
||||
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
}
|
||||
)
|
||||
response = HttpResponseRedirect(url)
|
||||
except ActivityUploadFailed as e: # pragma: no cover
|
||||
message = "Strava Upload error: %s" % e
|
||||
messages.error(request,message)
|
||||
w.uploadedtostrava = -1
|
||||
w.save()
|
||||
os.remove(tcxfile)
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
})
|
||||
response = HttpResponseRedirect(url)
|
||||
|
||||
return response
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
}
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Upload workout to Concept2 logbook
|
||||
@login_required()
|
||||
@@ -205,15 +109,12 @@ def workout_c2_upload_view(request,id=0):
|
||||
dologging(s)
|
||||
|
||||
try:
|
||||
message,c2id = c2stuff.workout_c2_upload(request.user,w)
|
||||
message,c2id = c2stuff.workout_c2_upload(request.user,w,asynchron=True)
|
||||
except NoTokenError: # pragma: no cover
|
||||
print('jet')
|
||||
return HttpResponseRedirect("/rowers/me/c2authorize/")
|
||||
|
||||
if message and c2id <=0: # pragma: no cover
|
||||
messages.error(request,message)
|
||||
elif message:
|
||||
messages.info(request,message)
|
||||
|
||||
messages.info(request,'Your workout will be synchronized to the Concept2 Logbook in the background')
|
||||
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
@@ -221,131 +122,9 @@ def workout_c2_upload_view(request,id=0):
|
||||
})
|
||||
|
||||
|
||||
response = HttpResponseRedirect(url)
|
||||
|
||||
return response
|
||||
|
||||
# Upload workout to RunKeeper
|
||||
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
|
||||
def workout_runkeeper_upload_view(request,id=0):
|
||||
message = ""
|
||||
w = get_workout(id)
|
||||
r = w.user
|
||||
|
||||
try:
|
||||
thetoken = runkeeper_open(r.user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
return HttpResponseRedirect("/rowers/me/runkeeperauthorize/")
|
||||
|
||||
# ready to upload. Hurray
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
data = runkeeperstuff.createrunkeeperworkoutdata(w)
|
||||
if not data: # pragma: no cover
|
||||
message = "Data error"
|
||||
messages.error(request,message)
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':id,
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
authorizationstring = str('Bearer ' + thetoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json',
|
||||
'Content-Length':'nnn'}
|
||||
|
||||
url = "https://api.runkeeper.com/fitnessActivities"
|
||||
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
|
||||
|
||||
# check for duplicate error first
|
||||
if (response.status_code == 409 ): # pragma: no cover # pragma: no cover
|
||||
message = "Duplicate error"
|
||||
messages.error(request,message)
|
||||
w.uploadedtorunkeeper = -1
|
||||
w.save()
|
||||
elif (response.status_code == 201 or response.status_code==200):
|
||||
runkeeperid = runkeeperstuff.getidfromresponse(response)
|
||||
w.uploadedtorunkeeper = runkeeperid
|
||||
w.save()
|
||||
url = reverse('workout_edit_view',
|
||||
kwargs={'id':encoder.encode_hex(w.id)})
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
else: # pragma: no cover
|
||||
s = response
|
||||
message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text)
|
||||
messages.error(request,message)
|
||||
|
||||
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
}) # pragma: no cover
|
||||
|
||||
return HttpResponseRedirect(url) # pragma: no cover
|
||||
|
||||
# Upload workout to Underarmour
|
||||
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
|
||||
def workout_underarmour_upload_view(request,id=0):
|
||||
message = ""
|
||||
w = get_workout(id)
|
||||
r = w.user
|
||||
|
||||
try:
|
||||
thetoken = underarmour_open(r.user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
|
||||
|
||||
# ready to upload. Hurray
|
||||
|
||||
|
||||
data = underarmourstuff.createunderarmourworkoutdata(w)
|
||||
if not data: # pragma: no cover
|
||||
message = "Data error"
|
||||
messages.error(request,message)
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
authorizationstring = str('Bearer ' + thetoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
url = "https://api.ua.com/v7.1/workout/"
|
||||
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
|
||||
|
||||
|
||||
# check for duplicate error first
|
||||
if (response.status_code == 409 ): # pragma: no cover # pragma: no cover
|
||||
message = "Duplicate error"
|
||||
messages.error(request,message)
|
||||
w.uploadedtounderarmour = -1
|
||||
w.save()
|
||||
elif (response.status_code == 201 or response.status_code==200):
|
||||
underarmourid = underarmourstuff.getidfromresponse(response)
|
||||
w.uploadedtounderarmour = underarmourid
|
||||
w.save()
|
||||
url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
else: # pragma: no cover
|
||||
s = response
|
||||
message = "Something went wrong in workout_underarmour_upload_view: %s " % s.reason
|
||||
messages.error(request,message)
|
||||
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
}) # pragma: no cover
|
||||
|
||||
return HttpResponseRedirect(url) # pragma: no cover
|
||||
|
||||
# Upload workout to SportTracks
|
||||
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid)
|
||||
@@ -355,53 +134,9 @@ def workout_sporttracks_upload_view(request,id=0):
|
||||
w = get_workout(id)
|
||||
r = w.user
|
||||
|
||||
try:
|
||||
thetoken = sporttracks_open(r.user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
return HttpResponseRedirect("/rowers/me/sporttracksauthorize/")
|
||||
message, res = sporttracksstuff.workout_sporttracks_upload(r.user,w,asynchron=True)
|
||||
|
||||
|
||||
|
||||
data = sporttracksstuff.createsporttracksworkoutdata(w)
|
||||
|
||||
if not data: # pragma: no cover
|
||||
message = "Data error"
|
||||
messages.error(request,message)
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
authorizationstring = str('Bearer ' + thetoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
|
||||
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json"
|
||||
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
|
||||
|
||||
|
||||
# check for duplicate error first
|
||||
if (response.status_code == 409 ): # pragma: no cover
|
||||
message = "Duplicate error"
|
||||
messages.error(request,message)
|
||||
w.uploadedtosporttracks = -1
|
||||
w.save()
|
||||
elif (response.status_code == 201 or response.status_code==200):
|
||||
s= response.json()
|
||||
sporttracksid = sporttracksstuff.getidfromresponse(response)
|
||||
w.uploadedtosporttracks = sporttracksid
|
||||
w.save()
|
||||
message = "Upload to SportTracks was successful"
|
||||
messages.info(request,message)
|
||||
|
||||
url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
|
||||
return HttpResponseRedirect(url)
|
||||
else: # pragma: no cover
|
||||
s = response
|
||||
message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason
|
||||
messages.error(request,message)
|
||||
messages.info(request,'Your workout will be synchronized with SportTracks in the background')
|
||||
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
@@ -444,6 +179,7 @@ def rower_c2_authorize(request): # pragma: no cover
|
||||
"redirect_uri": C2_REDIRECT_URI}
|
||||
url = "http://log.concept2.com/oauth/authorize?"+ urllib.parse.urlencode(params)
|
||||
url += "&scope="+scope
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Garmin authorization
|
||||
@@ -489,23 +225,6 @@ def rower_polar_authorize(request): # pragma: no cover
|
||||
|
||||
|
||||
|
||||
# Runkeeper authorization
|
||||
@login_required()
|
||||
def rower_runkeeper_authorize(request): # pragma: no cover
|
||||
# Generate a random string for the state parameter
|
||||
# Save it for use later to prevent xsrf attacks
|
||||
|
||||
state = str(uuid4())
|
||||
|
||||
params = {"client_id": RUNKEEPER_CLIENT_ID,
|
||||
"response_type": "code",
|
||||
"state": state,
|
||||
"redirect_uri": RUNKEEPER_REDIRECT_URI}
|
||||
|
||||
url = "https://runkeeper.com/apps/authorize?"+ urllib.parse.urlencode(params)
|
||||
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# SportTracks Authorization
|
||||
@login_required()
|
||||
@@ -525,24 +244,8 @@ def rower_sporttracks_authorize(request): # pragma: no cover
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Underarmour Authorization
|
||||
@login_required()
|
||||
def rower_underarmour_authorize(request): # pragma: no cover
|
||||
# Generate a random string for the state parameter
|
||||
# Save it for use later to prevent xsrf attacks
|
||||
|
||||
state = str(uuid4())
|
||||
|
||||
redirect_uri = UNDERARMOUR_REDIRECT_URI
|
||||
|
||||
url = 'https://www.mapmyfitness.com/v7.1/oauth2/authorize/?' \
|
||||
'client_id={0}&response_type=code&redirect_uri={1}'.format(
|
||||
UNDERARMOUR_CLIENT_KEY, redirect_uri
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Underarmour Authorization
|
||||
# RP3 Authorization
|
||||
@login_required()
|
||||
def rower_rp3_authorize(request): # pragma: no cover
|
||||
# Generate a random string for the state parameter
|
||||
@@ -557,7 +260,7 @@ def rower_rp3_authorize(request): # pragma: no cover
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Underarmour Authorization
|
||||
# TrainingPeaks Authorization
|
||||
@login_required()
|
||||
def rower_tp_authorize(request): # pragma: no cover
|
||||
# Generate a random string for the state parameter
|
||||
@@ -603,33 +306,6 @@ def rower_c2_token_refresh(request):
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# Underarmour token refresh. URL for manual refresh. Not visible to users
|
||||
@login_required()
|
||||
def rower_underarmour_token_refresh(request):
|
||||
r = getrower(request.user)
|
||||
res = underarmourstuff.do_refresh_token(
|
||||
r.underarmourrefreshtoken,
|
||||
r.underarmourtoken
|
||||
)
|
||||
access_token = res[0]
|
||||
expires_in = res[1]
|
||||
refresh_token = res[2]
|
||||
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
|
||||
|
||||
r = getrower(request.user)
|
||||
r.underarmourtoken = access_token
|
||||
r.underarmourtokenexpirydate = expirydatetime
|
||||
r.underarmourrefreshtoken = refresh_token
|
||||
|
||||
r.save()
|
||||
|
||||
successmessage = "Tokens refreshed. Good to go"
|
||||
messages.info(request,successmessage)
|
||||
|
||||
url = reverse('workouts_view')
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
# TrainingPeaks token refresh. URL for manual refresh. Not visible to users
|
||||
@@ -1026,31 +702,6 @@ def rower_process_stravacallback(request):
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# Process Runkeeper callback
|
||||
@login_required()
|
||||
def rower_process_runkeepercallback(request):
|
||||
code = request.GET['code']
|
||||
res = runkeeperstuff.get_token(code)
|
||||
access_token = res[0]
|
||||
|
||||
if access_token == 0:# pragma: no cover
|
||||
messages.error(request,"Something went wrong importing the token")
|
||||
url = reverse('workouts_view')
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
r = getrower(request.user)
|
||||
r.runkeepertoken = access_token
|
||||
|
||||
r.save()
|
||||
|
||||
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
|
||||
messages.info(request,successmessage)
|
||||
url = reverse('rower_exportsettings_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
# Process SportTracks callback
|
||||
@@ -1086,29 +737,6 @@ def rower_process_sporttrackscallback(request):
|
||||
|
||||
|
||||
|
||||
# Process Underarmour callback
|
||||
@login_required()
|
||||
def rower_process_underarmourcallback(request):
|
||||
code = request.GET['code']
|
||||
res = underarmourstuff.get_token(code)
|
||||
|
||||
|
||||
access_token = res[0]
|
||||
expires_in = res[1]
|
||||
refresh_token = res[2]
|
||||
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
|
||||
|
||||
r = getrower(request.user)
|
||||
r.underarmourtoken = access_token
|
||||
r.underarmourtokenexpirydate = expirydatetime
|
||||
r.underarmourrefreshtoken = refresh_token
|
||||
|
||||
r.save()
|
||||
|
||||
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
|
||||
messages.info(request,successmessage)
|
||||
url = reverse('rower_exportsettings_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Process RP3 callback
|
||||
@login_required()
|
||||
@@ -1624,112 +1252,7 @@ def garmin_details_view(request):
|
||||
return HttpResponse(status=200)
|
||||
|
||||
|
||||
# The page where you select which RunKeeper workout to import
|
||||
@login_required()
|
||||
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
|
||||
def workout_runkeeperimport_view(request,message="",userid=0):
|
||||
res = runkeeperstuff.get_runkeeper_workout_list(request.user)
|
||||
if (res.status_code != 200):
|
||||
if (res.status_code == 401):
|
||||
r = getrower(request.user)
|
||||
if (r.runkeepertoken == '') or (r.runkeepertoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return HttpResponseRedirect("/rowers/me/runkeeperauthorize/")
|
||||
message = "Something went wrong in workout_runkeeperimport_view" # pragma: no cover
|
||||
messages.error(request,message) # pragma: no cover
|
||||
|
||||
if settings.DEBUG: # pragma: no cover
|
||||
return HttpResponse(res)
|
||||
else: # pragma: no cover
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
workouts = []
|
||||
for item in res.json()['items']:
|
||||
d = int(float(item['total_distance']))
|
||||
i = getidfromuri(item['uri'])
|
||||
ttot = str(datetime.timedelta(seconds=int(float(item['duration']))))
|
||||
s = item['start_time']
|
||||
r = item['type']
|
||||
keys = ['id','distance','duration','starttime','type']
|
||||
values = [i,d,ttot,s,r]
|
||||
|
||||
res = dict(zip(keys,values))
|
||||
workouts.append(res)
|
||||
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':'/rowers/list-workouts/',
|
||||
'name':'Workouts'
|
||||
},
|
||||
{
|
||||
'url':reverse('workout_runkeeperimport_view'),
|
||||
'name':'Runkeeper'
|
||||
}
|
||||
]
|
||||
|
||||
r = getrower(request.user)
|
||||
|
||||
return render(request,'runkeeper_list_import.html',
|
||||
{'workouts':workouts,
|
||||
'rower':r,
|
||||
'active':'nav-workouts',
|
||||
'breadcrumbs':breadcrumbs,
|
||||
'teams':get_my_teams(request.user),
|
||||
})
|
||||
|
||||
return HttpResponse(res) # pragma: no cover
|
||||
|
||||
# The page where you select which RunKeeper workout to import
|
||||
@login_required()
|
||||
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
|
||||
def workout_underarmourimport_view(request,message="",userid=0):
|
||||
res = underarmourstuff.get_underarmour_workout_list(request.user)
|
||||
if (res.status_code != 200):
|
||||
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
|
||||
|
||||
workouts = []
|
||||
items = res.json()['_embedded']['workouts']
|
||||
for item in items:
|
||||
s = item['start_datetime']
|
||||
i,r = underarmourstuff.get_idfromuri(request.user,item['_links'])
|
||||
n = item['name']
|
||||
try:
|
||||
d = item['aggregates']['distance_total']
|
||||
except KeyError: # pragma: no cover
|
||||
d = 0
|
||||
try:
|
||||
ttot = item['aggregates']['active_time_total']
|
||||
except KeyError:
|
||||
ttot = 0
|
||||
|
||||
keys = ['id','distance','duration','starttime','type']
|
||||
values = [i,d,ttot,s,r]
|
||||
thedict = dict(zip(keys,values))
|
||||
|
||||
workouts.append(thedict)
|
||||
|
||||
rower = getrower(request.user)
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':'/rowers/list-workouts/',
|
||||
'name':'Workouts'
|
||||
},
|
||||
{
|
||||
'url':reverse('workout_c2import_view'),
|
||||
'name':'Concept2'
|
||||
},
|
||||
]
|
||||
|
||||
return render(request,'underarmour_list_import.html',
|
||||
{'workouts':workouts,
|
||||
'breadcrumbs':breadcrumbs,
|
||||
'rower':rower,
|
||||
'active':'nav-workouts',
|
||||
'teams':get_my_teams(request.user),
|
||||
})
|
||||
|
||||
return HttpResponse(res) # pragma: no cover
|
||||
|
||||
# the page where you select which Polar workout to Import
|
||||
@login_required()
|
||||
@@ -2029,22 +1552,28 @@ importlistviews = {
|
||||
'strava':'workout_stravaimport_view',
|
||||
'polar':'workout_polarimport_view',
|
||||
'ownapi':'workout_view',
|
||||
'runkeeper':'workout_runkeeperimport_view',
|
||||
'sporttracks':'workout_sporttracksimport_view',
|
||||
'trainingpeaks':'workout_view',
|
||||
'underarmour':'workout_underarmourimport_view',
|
||||
'nk':'workout_nkimport_view',
|
||||
}
|
||||
|
||||
importauthorizeviews = {
|
||||
'c2':'rower_c2_authorize',
|
||||
'strava':'rower_strava_authorize',
|
||||
'polar':'rower_polar_authorize',
|
||||
'ownapi':'workout_view',
|
||||
'sporttracks':'rower_sporttracks_authorize',
|
||||
'trainingpeaks':'rower_tp_authorize',
|
||||
'nk':'rower_nk_authorize',
|
||||
}
|
||||
|
||||
importsources = {
|
||||
'c2':c2stuff,
|
||||
'strava':stravastuff,
|
||||
'polar':polarstuff,
|
||||
'ownapi':ownapistuff,
|
||||
'runkeeper':runkeeperstuff,
|
||||
'sporttracks':sporttracksstuff,
|
||||
'trainingpeaks':tpstuff,
|
||||
'underarmour':underarmourstuff,
|
||||
'nk':nkstuff,
|
||||
}
|
||||
|
||||
@@ -2071,178 +1600,24 @@ def workout_getrp3importview(request,externalid):
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@login_required()
|
||||
def workout_getimportview(request,externalid,source = 'c2',do_async=False):
|
||||
data,strokedata = importsources[source].get_workout(request.user,externalid,
|
||||
do_async=do_async)
|
||||
def workout_getimportview(request,externalid,source = 'c2',do_async=True):
|
||||
try:
|
||||
result = importsources[source].get_workout(request.user,externalid,
|
||||
do_async=do_async)
|
||||
except NoTokenError:
|
||||
|
||||
if do_async: # pragma: no cover
|
||||
return HttpResponseRedirect(reverse(importauthorizeviews[source]))
|
||||
|
||||
if result: # pragma: no cover
|
||||
messages.info(request,"Your workout will be imported in the background")
|
||||
# this should return to the respective import list page
|
||||
url = reverse(importlistviews[source])
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
if not data: # pragma: no cover
|
||||
messages.error(request,"No strokedata received")
|
||||
url = reverse('workouts_view')
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
try:
|
||||
workouttype = mytypes.c2mappinginv[data['type']]
|
||||
except KeyError:
|
||||
workouttype = 'rower'
|
||||
|
||||
|
||||
# Now works only for C2
|
||||
try:
|
||||
if strokedata == 0:
|
||||
messages.error(request,'An error occurred importing the workout from Concept2')
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
if strokedata.empty: # pragma: no cover
|
||||
distance = data['distance']
|
||||
c2id = data['id']
|
||||
workouttype = mytypes.c2mappinginv[data['type']]
|
||||
verified = data['verified']
|
||||
startdatetime = iso8601.parse_date(data['date'])
|
||||
weightclass = data['weight_class']
|
||||
weightcategory = 'hwt'
|
||||
if weightclass == "L":
|
||||
weightcategory = 'lwt'
|
||||
totaltime = data['time']/10.
|
||||
duration = dataprep.totaltime_sec_to_string(totaltime)
|
||||
duration = datetime.datetime.strptime(duration,'%H:%M:%S.%f').time()
|
||||
|
||||
try:
|
||||
timezone_str = data['timezone']
|
||||
except:
|
||||
timezone_str = 'UTC'
|
||||
|
||||
if timezone_str is None:
|
||||
timezone_str = 'UTC'
|
||||
|
||||
|
||||
|
||||
|
||||
workoutdate = startdatetime.astimezone(
|
||||
pytz.timezone(timezone_str)
|
||||
).strftime('%Y-%m-%d')
|
||||
starttime = startdatetime.astimezone(
|
||||
pytz.timezone(timezone_str)
|
||||
).strftime('%H:%M:%S')
|
||||
|
||||
try:
|
||||
notes = data['comments']
|
||||
name = notes[:40]
|
||||
except (KeyError,TypeError):
|
||||
comments = 'C2 Import Workout from {startdatetime}'.format(startdatetime=startdatetime)
|
||||
name = notes
|
||||
|
||||
r = getrower(request.user)
|
||||
|
||||
id, message = dataprep.create_row_df(r,
|
||||
distance,
|
||||
duration,
|
||||
startdatetime,
|
||||
workouttype=workouttype)
|
||||
|
||||
w = Workout.objects.get(id=id)
|
||||
w.uploadedtoc2 = c2id
|
||||
w.name = name
|
||||
w.notes = notes
|
||||
w.workouttype = workouttype
|
||||
w.save()
|
||||
|
||||
message = "This workout does not have any stroke data associated with it. We created synthetic stroke data."
|
||||
messages.info(request,message)
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id),
|
||||
})
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# strokedata not empty - continue
|
||||
id,message = importsources[source].add_workout_from_data(
|
||||
request.user,
|
||||
externalid,data,
|
||||
strokedata,
|
||||
source=source,
|
||||
workoutsource=source)
|
||||
|
||||
w = get_workout(encoder.encode_hex(id))
|
||||
|
||||
if 'workout' in data: # pragma: no cover
|
||||
if 'splits' in data['workout']:
|
||||
splitdata = data['workout']['splits']
|
||||
elif 'intervals' in data['workout']:
|
||||
splitdata = data['workout']['intervals']
|
||||
else:
|
||||
splitdata = False
|
||||
else:
|
||||
splitdata = False
|
||||
|
||||
# splitdata (only for C2)
|
||||
if splitdata: # pragma: no cover
|
||||
w.summary,sa,results = c2stuff.summaryfromsplitdata(splitdata,data,w.csvfilename,workouttype=workouttype)
|
||||
w.save()
|
||||
|
||||
from rowingdata.trainingparser import getlist
|
||||
# set stroke data in CSV file
|
||||
if sa:
|
||||
values = getlist(sa)
|
||||
units = getlist(sa,sel='unit')
|
||||
types = getlist(sa,sel='type')
|
||||
|
||||
rowdata = rdata(csvfile=w.csvfilename)
|
||||
if rowdata:
|
||||
rowdata.updateintervaldata(values,
|
||||
units,types,results)
|
||||
|
||||
rowdata.write_csv(w.csvfilename,gzip=True)
|
||||
dataprep.update_strokedata(w.id,rowdata.df)
|
||||
|
||||
|
||||
|
||||
if source == 'strava':
|
||||
w.uploadedtostrava = externalid
|
||||
elif source == 'c2':
|
||||
w.uploadedtoc2 = externalid
|
||||
elif source == 'polar': # pragma: no cover
|
||||
w.uploadedtopolar = externalid
|
||||
elif source == 'runkeeper':
|
||||
w.uploadedtorunkeeper = externalid
|
||||
elif source == 'sporttracks':
|
||||
w.uploadedtosporttracks = externalid
|
||||
elif source == 'trainingpeaks': # pragma: no cover
|
||||
w.uploadedtotp = externalid
|
||||
elif source == 'underarmour':
|
||||
w.uploadedtounderarmour = externalid
|
||||
|
||||
w.save()
|
||||
|
||||
if message: # pragma: no cover
|
||||
messages.error(request,message)
|
||||
|
||||
r = getrower(request.user)
|
||||
|
||||
url = reverse(r.defaultlandingpage,
|
||||
kwargs = {
|
||||
'id':encoder.encode_hex(w.id)
|
||||
})
|
||||
else: # pragma: no cover
|
||||
messages.error(request,'Error getting the workout')
|
||||
|
||||
url = reverse(importlistviews[source])
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Imports all new workouts from SportTracks
|
||||
@login_required()
|
||||
def workout_getsporttracksworkout_all(request):
|
||||
@@ -2255,14 +1630,11 @@ def workout_getsporttracksworkout_all(request):
|
||||
])
|
||||
newids = [stid for stid in stids if not stid in knownstids]
|
||||
for sporttracksid in newids:
|
||||
data,strokedata = sporttracksstuff.get_workout(
|
||||
id = sporttracksstuff.get_workout(
|
||||
request.user,sporttracksid)
|
||||
|
||||
id,message = sporttracksstuff.add_workout_from_data(
|
||||
request.user,sporttracksid,data,strokedata
|
||||
)
|
||||
if id==0: # pragma: no cover
|
||||
messages.error(request,message)
|
||||
messages.error(request,"Something went wrong with workout {id}".format(id=sporttracksid))
|
||||
|
||||
else:
|
||||
w = Workout.objects.get(id=id)
|
||||
|
||||
+2
-167
@@ -158,7 +158,6 @@ import rowers.nkstuff as nkstuff
|
||||
from rowers.c2stuff import c2_open
|
||||
from rowers.nkstuff import nk_open
|
||||
from rowers.rp3stuff import rp3_open
|
||||
from rowers.runkeeperstuff import runkeeper_open
|
||||
from rowers.sporttracksstuff import sporttracks_open
|
||||
from rowers.tpstuff import tp_open
|
||||
from iso8601 import ParseError
|
||||
@@ -167,10 +166,9 @@ import rowers.garmin_stuff as garmin_stuff
|
||||
from rowers.stravastuff import strava_open
|
||||
import rowers.polarstuff as polarstuff
|
||||
import rowers.sporttracksstuff as sporttracksstuff
|
||||
import rowers.underarmourstuff as underarmourstuff
|
||||
from rowers.underarmourstuff import underarmour_open
|
||||
|
||||
|
||||
import rowers.tpstuff as tpstuff
|
||||
import rowers.runkeeperstuff as runkeeperstuff
|
||||
import rowers.rp3stuff as rp3stuff
|
||||
import rowers.ownapistuff as ownapistuff
|
||||
from rowers.ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
|
||||
@@ -180,9 +178,6 @@ from rowsandall_app.settings import (
|
||||
POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET,
|
||||
SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI,
|
||||
SPORTTRACKS_CLIENT_SECRET,
|
||||
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_REDIRECT_URI,
|
||||
UNDERARMOUR_CLIENT_SECRET,UNDERARMOUR_CLIENT_KEY,
|
||||
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
|
||||
TP_CLIENT_ID,TP_REDIRECT_URI,TP_CLIENT_KEY,TP_CLIENT_SECRET,
|
||||
RP3_CLIENT_ID,RP3_REDIRECT_URI,RP3_CLIENT_KEY,RP3_CLIENT_SECRET,
|
||||
BRAINTREE_MERCHANT_ID,BRAINTREE_PUBLIC_KEY,BRAINTREE_PRIVATE_KEY,
|
||||
@@ -1278,166 +1273,6 @@ def sendmail(request):
|
||||
return HttpResponseRedirect('/rowers/email/')
|
||||
|
||||
|
||||
# Create workout data from Strava or Concept2
|
||||
# data and create the associated Workout object and save it
|
||||
def add_workout_from_strokedata(user,importid,data,strokedata,
|
||||
source='c2',splitdata=None,
|
||||
workoutsource='concept2'):
|
||||
try:
|
||||
workouttype = data['type']
|
||||
except KeyError: # pragma: no cover
|
||||
workouttype = 'rower'
|
||||
|
||||
if workouttype not in [x[0] for x in Workout.workouttypes]: # pragma: no cover
|
||||
workouttype = 'other'
|
||||
try:
|
||||
comments = data['comments']
|
||||
except: # pragma: no cover
|
||||
comments = ' '
|
||||
|
||||
# comments = "Imported data \n %s" % comments
|
||||
# comments = "Imported data \n"+comments # str(comments)
|
||||
try:
|
||||
thetimezone = tz(data['timezone'])
|
||||
except: # pragma: no cover
|
||||
thetimezone = 'UTC'
|
||||
|
||||
r = getrower(user)
|
||||
try:
|
||||
rowdatetime = iso8601.parse_date(data['date_utc'])
|
||||
except KeyError: # pragma: no cover
|
||||
rowdatetime = iso8601.parse_date(data['start_date'])
|
||||
except ParseError: # pragma: no cover
|
||||
rowdatetime = iso8601.parse_date(data['date'])
|
||||
|
||||
|
||||
try:
|
||||
c2intervaltype = data['workout_type']
|
||||
|
||||
except KeyError: # pragma: no cover
|
||||
c2intervaltype = ''
|
||||
|
||||
try:
|
||||
title = data['name']
|
||||
except KeyError: # pragma: no cover
|
||||
title = ""
|
||||
try:
|
||||
t = data['comments'].split('\n', 1)[0]
|
||||
title += t[:20]
|
||||
except:
|
||||
title = ''
|
||||
|
||||
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.loc[:,'t']
|
||||
|
||||
nr_rows = len(unixtime)
|
||||
|
||||
try: # pragma: no cover
|
||||
latcoord = strokedata.loc[:,'lat']
|
||||
loncoord = strokedata.loc[:,'lon']
|
||||
except:
|
||||
latcoord = np.zeros(nr_rows)
|
||||
loncoord = np.zeros(nr_rows)
|
||||
|
||||
|
||||
try:
|
||||
strokelength = strokedata.loc[:,'strokelength']
|
||||
except:
|
||||
strokelength = np.zeros(nr_rows)
|
||||
|
||||
dist2 = 0.1*strokedata.loc[:,'d']
|
||||
|
||||
try:
|
||||
spm = strokedata.loc[:,'spm']
|
||||
except KeyError: # pragma: no cover
|
||||
spm = 0*dist2
|
||||
|
||||
try:
|
||||
hr = strokedata.loc[:,'hr']
|
||||
except KeyError:
|
||||
hr = 0*spm
|
||||
pace = strokedata.loc[:,'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: # pragma: no cover
|
||||
totaldist = 0
|
||||
totaltime = 0
|
||||
else: # pragma: no cover
|
||||
totaldist = 0
|
||||
totaltime = 0
|
||||
|
||||
id,message = dataprep.save_workout_database(
|
||||
csvfilename,r,
|
||||
workouttype=workouttype,
|
||||
dosmooth=r.dosmooth,
|
||||
title=title,notes=comments,
|
||||
# totaldist=totaldist,
|
||||
# totaltime=totaltime,
|
||||
workoutsource=workoutsource,
|
||||
dosummary=True
|
||||
)
|
||||
|
||||
|
||||
|
||||
return id,message
|
||||
|
||||
|
||||
|
||||
|
||||
def keyvalue_get_default(key,options,def_options): # pragma: no cover
|
||||
|
||||
try:
|
||||
|
||||
@@ -329,7 +329,6 @@ def rower_exportsettings_view(request,userid=0):
|
||||
'polar_auto_import':'polartoken',
|
||||
'c2_auto_export':'c2token',
|
||||
'c2_auto_import':'c2token',
|
||||
'mapmyfitness_auto_export':'underarmourtoken',
|
||||
'runkeeper_auto_export':'runkeepertoken',
|
||||
'sporttracks_auto_export':'sporttrackstoken',
|
||||
'strava_auto_export':'stravatoken',
|
||||
|
||||
@@ -4864,8 +4864,6 @@ def workout_upload_api(request):
|
||||
upload_to_c2 = optionsform.cleaned_data['upload_to_C2']
|
||||
upload_to_strava = optionsform.cleaned_data['upload_to_Strava']
|
||||
upload_to_st = optionsform.cleaned_data['upload_to_SportTracks']
|
||||
upload_to_rk = optionsform.cleaned_data['upload_to_RunKeeper']
|
||||
upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness']
|
||||
upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks']
|
||||
makeprivate = optionsform.cleaned_data['makeprivate']
|
||||
else: # pragma: no cover
|
||||
@@ -5042,8 +5040,6 @@ def workout_upload_view(request,
|
||||
upload_to_c2 = uploadoptions.get('upload_to_C2',False)
|
||||
upload_to_strava = uploadoptions.get('upload_to_Strava',False)
|
||||
upload_to_st = uploadoptions.get('upload_to_SportTracks',False)
|
||||
upload_to_rk = uploadoptions.get('upload_to_RunKeeper',False)
|
||||
upload_to_ua = uploadoptions.get('upload_to_MapMyFitness',False)
|
||||
upload_to_tp = uploadoptions.get('upload_to_TrainingPeaks',False)
|
||||
|
||||
response = {}
|
||||
@@ -5093,8 +5089,6 @@ def workout_upload_view(request,
|
||||
upload_to_c2 = optionsform.cleaned_data['upload_to_C2']
|
||||
upload_to_strava = optionsform.cleaned_data['upload_to_Strava']
|
||||
upload_to_st = optionsform.cleaned_data['upload_to_SportTracks']
|
||||
upload_to_rk = optionsform.cleaned_data['upload_to_RunKeeper']
|
||||
upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness']
|
||||
upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks']
|
||||
makeprivate = optionsform.cleaned_data['makeprivate']
|
||||
landingpage = optionsform.cleaned_data['landingpage']
|
||||
@@ -5112,8 +5106,6 @@ def workout_upload_view(request,
|
||||
'upload_to_C2':upload_to_c2,
|
||||
'upload_to_Strava':upload_to_strava,
|
||||
'upload_to_SportTracks':upload_to_st,
|
||||
'upload_to_RunKeeper':upload_to_rk,
|
||||
'upload_to_MapMyFitness':upload_to_ua,
|
||||
'upload_to_TrainingPeaks':upload_to_tp,
|
||||
'landingpage':landingpage,
|
||||
'boattype': boattype,
|
||||
@@ -5255,35 +5247,6 @@ def workout_upload_view(request,
|
||||
else:
|
||||
messages.error(request,message)
|
||||
|
||||
if (upload_to_rk): # pragma: no cover
|
||||
try:
|
||||
message,id = runkeeperstuff.workout_runkeeper_upload(
|
||||
request.user,w
|
||||
)
|
||||
except NoTokenError:
|
||||
message = "Please connect to Runkeeper first"
|
||||
id = 0
|
||||
|
||||
if id>1:
|
||||
messages.info(request,message)
|
||||
else:
|
||||
messages.error(request,message)
|
||||
|
||||
|
||||
if (upload_to_ua): # pragma: no cover
|
||||
try:
|
||||
message,id = underarmourstuff.workout_ua_upload(
|
||||
request.user,w
|
||||
)
|
||||
except NoTokenError:
|
||||
message = "Please connect to MapMyFitness first"
|
||||
id = 0
|
||||
|
||||
if id>1:
|
||||
messages.info(request,message)
|
||||
else:
|
||||
messages.error(request,message)
|
||||
|
||||
|
||||
if (upload_to_tp): # pragma: no cover
|
||||
try:
|
||||
@@ -5462,14 +5425,9 @@ def workout_upload_view(request,
|
||||
if r.sporttracks_auto_export and ispromember(r.user): # pragma: no cover
|
||||
uploadoptions['upload_to_SportTracks'] = True
|
||||
|
||||
if r.runkeeper_auto_export and ispromember(r.user): # pragma: no cover
|
||||
uploadoptions['upload_to_RunKeeper'] = True
|
||||
|
||||
if r.trainingpeaks_auto_export and ispromember(r.user): # pragma: no cover
|
||||
uploadoptions['upload_to_TrainingPeaks'] = True
|
||||
|
||||
if r.mapmyfitness_auto_export and ispromember(r.user): # pragma: no cover
|
||||
uploadoptions['upload_to_MapMyFitness'] = True
|
||||
|
||||
form = DocumentsForm(initial=docformoptions)
|
||||
optionsform = UploadOptionsForm(initial=uploadoptions,
|
||||
|
||||
@@ -74,9 +74,7 @@ urlpatterns += [
|
||||
re_path(r'^stravacall\_back',rowersviews.rower_process_stravacallback),
|
||||
re_path(r'^garmin\_callback',rowersviews.rower_process_garmincallback),
|
||||
re_path(r'^sporttracks\_callback',rowersviews.rower_process_sporttrackscallback),
|
||||
re_path(r'^underarmour\_callback',rowersviews.rower_process_underarmourcallback),
|
||||
re_path(r'^polarflowcallback',rowersviews.rower_process_polarcallback),
|
||||
re_path(r'^runkeeper\_callback',rowersviews.rower_process_runkeepercallback),
|
||||
re_path(r'^tp\_callback',rowersviews.rower_process_tpcallback),
|
||||
re_path(r'^rp3\_callback',rowersviews.rower_process_rp3callback),
|
||||
re_path(r'^twitter\_callback',rowersviews.rower_process_twittercallback),
|
||||
|
||||
Reference in New Issue
Block a user