|
|
|
|
@@ -286,66 +286,6 @@ def workout_runkeeper_upload_view(request,id=0):
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
@@ -525,24 +465,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 +481,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 +527,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
|
|
|
|
|
@@ -1086,29 +983,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()
|
|
|
|
|
@@ -1680,56 +1554,6 @@ def workout_runkeeperimport_view(request,message="",userid=0):
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
@@ -2032,7 +1856,6 @@ importlistviews = {
|
|
|
|
|
'runkeeper':'workout_runkeeperimport_view',
|
|
|
|
|
'sporttracks':'workout_sporttracksimport_view',
|
|
|
|
|
'trainingpeaks':'workout_view',
|
|
|
|
|
'underarmour':'workout_underarmourimport_view',
|
|
|
|
|
'nk':'workout_nkimport_view',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2044,7 +1867,6 @@ importsources = {
|
|
|
|
|
'runkeeper':runkeeperstuff,
|
|
|
|
|
'sporttracks':sporttracksstuff,
|
|
|
|
|
'trainingpeaks':tpstuff,
|
|
|
|
|
'underarmour':underarmourstuff,
|
|
|
|
|
'nk':nkstuff,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2071,178 +1893,20 @@ 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,
|
|
|
|
|
def workout_getimportview(request,externalid,source = 'c2',do_async=True):
|
|
|
|
|
result = importsources[source].get_workout(request.user,externalid,
|
|
|
|
|
do_async=do_async)
|
|
|
|
|
|
|
|
|
|
if do_async: # pragma: no cover
|
|
|
|
|
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)
|
|
|
|
|
})
|
|
|
|
|
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):
|
|
|
|
|
|