Private
Public Access
1
0

Merge branch 'feature/integrations' into develop

This commit is contained in:
Sander Roosendaal
2023-02-19 10:05:22 +01:00
43 changed files with 3337 additions and 4607 deletions
-920
View File
@@ -101,923 +101,3 @@ def getagegrouprecord(age, sex='male', weightcategory='hwt',
return power
oauth_data = {
'client_id': C2_CLIENT_ID,
'client_secret': C2_CLIENT_SECRET,
'redirect_uri': C2_REDIRECT_URI,
'autorization_uri': "https://log.concept2.com/oauth/authorize",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'c2token',
'refreshtokenname': 'c2refreshtoken',
'expirydatename': 'tokenexpirydate',
'bearer_auth': True,
'base_url': "https://log.concept2.com/oauth/access_token",
'scope': 'write',
}
# Checks if user has Concept2 tokens, resets tokens if they are
# expired.
def c2_open(user):
r = Rower.objects.get(user=user)
if (r.c2token == '') or (r.c2token is None):
raise NoTokenError("User has no token")
else:
if (timezone.now() > r.tokenexpirydate):
res = rower_c2_token_refresh(user)
if res is None: # pragma: no cover
raise NoTokenError("User has no token")
if res[0] is not None:
thetoken = res[0]
else: # pragma: no cover
raise NoTokenError("User has no token")
else:
thetoken = r.c2token
return thetoken
def get_c2_workouts(rower, page=1, do_async=True):
try:
_ = c2_open(rower.user)
except NoTokenError: # pragma: no cover
return 0
res = get_c2_workout_list(rower.user, page=page)
if (res.status_code != 200): # pragma: no cover
return 0
else:
c2ids = [item['id'] for item in res.json()['data']]
alldata = {}
for item in res.json()['data']:
alldata[item['id']] = item
knownc2ids = [
w.uploadedtoc2 for w in Workout.objects.filter(user=rower)
]
tombstones = [
t.uploadedtoc2 for t in TombStone.objects.filter(user=rower)
]
# get "blocked" c2ids
parkedids = []
try:
with open('c2blocked.json', 'r') as c2blocked:
jsondata = json.load(c2blocked)
parkedids = jsondata['ids']
except FileNotFoundError: # pragma: no cover
pass
knownc2ids = uniqify(knownc2ids+tombstones+parkedids)
newids = [c2id for c2id in c2ids if c2id not in knownc2ids]
if settings.TESTING:
newids = c2ids
newparkedids = uniqify(newids+parkedids)
with open('c2blocked.json', 'wt') as c2blocked:
data = {'ids': newparkedids}
json.dump(data, c2blocked)
counter = 0
for c2id in newids:
if do_async: # pragma: no cover
_ = myqueue(queuehigh,
handle_c2_async_workout,
alldata,
rower.user.id,
rower.c2token,
c2id,
counter,
rower.defaulttimezone)
counter = counter+1
else:
_ = create_async_workout(alldata, rower.user, c2id)
return 1
# get workout metrics, then relay stroke data to an asynchronous task
def create_async_workout(alldata, user, c2id):
data = alldata[c2id]
splitdata = None
c2id = data['id']
workouttype = data['type']
startdatetime = iso8601.parse_date(data['date'])
try:
title = data['name']
except KeyError:
title = ""
try:
t = data['comments'].split('\n', 1)[0]
title += t[:40]
except:
title = ''
# Create CSV file name and save data to CSV file
csvfilename = 'media/Import_'+str(c2id)+'.csv.gz'
r = Rower.objects.get(user=user)
authorizationstring = str('Bearer ' + r.c2token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
# url2 = "https://log.concept2.com/api/users/me/results"+str(c2id)
url = "https://log.concept2.com/api/users/me/results/"+str(c2id)+"/strokes"
try:
s = requests.get(url, headers=headers)
except ConnectionError: # pragma: no cover
return 0
if s.status_code != 200: # pragma: no cover
return 0
strokedata = pd.DataFrame.from_dict(s.json()['data'])
res = make_cumvalues(0.1*strokedata['t'])
cum_time = res[0]
lapidx = res[1]
starttimeunix = arrow.get(startdatetime).timestamp()
starttimeunix = starttimeunix-cum_time.max()
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 == 'bike': # pragma: no cover
velo = 1000./pace
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,
'cum_dist': dist2
})
df.sort_values(by='TimeStamp (sec)', ascending=True)
res = df.to_csv(csvfilename, index_label='index',
compression='gzip')
userid = r.user.id
uploadoptions = {
'secret': UPLOAD_SERVICE_SECRET,
'user': userid,
'file': csvfilename,
'title': title,
'workouttype': workouttype,
'boattype': '1x',
'c2id': c2id,
}
session = requests.session()
newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'}
session.headers.update(newHeaders)
response = session.post(UPLOAD_SERVICE_URL, json=uploadoptions)
if response.status_code != 200: # pragma: no cover
return 0
try:
workoutid = response.json()['id']
except KeyError: # pragma: no cover
workoutid = 1
newc2id = Workout.objects.get(id=workoutid).uploadedtoc2
parkedids = []
with open('c2blocked.json', 'r') as c2blocked:
jsondata = json.load(c2blocked)
parkedids = jsondata['ids']
newparkedids = [id for id in parkedids if id != newc2id]
with open('c2blocked.json', 'wt') as c2blocked:
data = {'ids': newparkedids}
c2blocked.seek(0)
json.dump(data, c2blocked)
# summary
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
if splitdata: # pragma: no cover
summary, sa, results = c2stuff.summaryfromsplitdata(
splitdata, data, csvfilename, workouttype=workouttype)
w = Workout.objects.get(id=workoutid)
w.summary = summary
w.save()
from rowingdata.trainingparser import getlist
if sa:
values = getlist(sa)
units = getlist(sa, sel='unit')
types = getlist(sa, sel='type')
rowdata = rdata(w.csvfilename)
if rowdata:
rowdata.updateintervaldata(values,
units, types, results)
rowdata.write_csv(w.csvfilename, gzip=True)
dataprep.update_strokedata(w.id, rowdata.df)
return workoutid
# convert datetime object to seconds
def makeseconds(t):
seconds = t.hour*3600.+t.minute*60.+t.second+0.1*int(t.microsecond/1.e5)
return seconds
# convert our weight class code to Concept2 weight class code
def c2wc(weightclass):
if (weightclass == "lwt"): # pragma: no cover
res = "L"
else:
res = "H"
return res
# Concept2 logbook sends over split data for each interval
# We use it here to generate a custom summary
# Some users complained about small differences
def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'):
workouttype = workouttype.lower()
totaldist = data['distance']
totaltime = data['time']/10.
try:
spm = data['stroke_rate']
except KeyError: # pragma: no cover
spm = 0
try:
resttime = data['rest_time']/10.
except KeyError: # pragma: no cover
resttime = 0
try:
restdistance = data['rest_distance']
except KeyError: # pragma: no cover
restdistance = 0
try:
avghr = data['heart_rate']['average']
except KeyError: # pragma: no cover
avghr = 0
try:
maxhr = data['heart_rate']['max']
except KeyError: # pragma: no cover
maxhr = 0
try:
avgpace = 500.*totaltime/totaldist
except (ZeroDivisionError, OverflowError): # pragma: no cover
avgpace = 0.
try:
restpace = 500.*resttime/restdistance
except (ZeroDivisionError, OverflowError): # pragma: no cover
restpace = 0.
velo = totaldist/totaltime
avgpower = 2.8*velo**(3.0)
if workouttype in ['bike', 'bikeerg']: # pragma: no cover
velo = velo/2.
avgpower = 2.8*velo**(3.0)
velo = velo*2
try:
restvelo = restdistance/resttime
except (ZeroDivisionError, OverflowError): # pragma: no cover
restvelo = 0
restpower = 2.8*restvelo**(3.0)
if workouttype in ['bike', 'bikeerg']: # pragma: no cover
restvelo = restvelo/2.
restpower = 2.8*restvelo**(3.0)
restvelo = restvelo*2
try:
avgdps = totaldist/data['stroke_count']
except (ZeroDivisionError, OverflowError, KeyError): # pragma: no cover
avgdps = 0
from rowingdata import summarystring, workstring, interval_string
sums = summarystring(totaldist, totaltime, avgpace, spm, avghr, maxhr,
avgdps, avgpower, readFile=filename,
separator=sep)
sums += workstring(totaldist, totaltime, avgpace, spm, avghr, maxhr,
avgdps, avgpower, separator=sep, symbol='W')
sums += workstring(restdistance, resttime, restpace, 0, 0, 0, 0, restpower,
separator=sep,
symbol='R')
sums += '\nWorkout Details\n'
sums += '#-{sep}SDist{sep}-Split-{sep}-SPace-{sep}-Pwr-{sep}SPM-{sep}AvgHR{sep}MaxHR{sep}DPS-\n'.format(
sep=sep
)
intervalnr = 0
sa = []
results = []
try:
timebased = data['workout_type'] in [
'FixedTimeSplits', 'FixedTimeInterval']
except KeyError: # pragma: no cover
timebased = False
for interval in splitdata:
try:
idist = interval['distance']
except KeyError: # pragma: no cover
idist = 0
try:
itime = interval['time']/10.
except KeyError: # pragma: no cover
itime = 0
try:
ipace = 500.*itime/idist
except (ZeroDivisionError, OverflowError): # pragma: no cover
ipace = 180.
try:
ispm = interval['stroke_rate']
except KeyError: # pragma: no cover
ispm = 0
try:
irest_time = interval['rest_time']/10.
except KeyError: # pragma: no cover
irest_time = 0
try:
iavghr = interval['heart_rate']['average']
except KeyError: # pragma: no cover
iavghr = 0
try:
imaxhr = interval['heart_rate']['average']
except KeyError: # pragma: no cover
imaxhr = 0
# create interval values
iarr = [idist, 'meters', 'work']
resarr = [itime]
if timebased: # pragma: no cover
iarr = [itime, 'seconds', 'work']
resarr = [idist]
if irest_time > 0:
iarr += [irest_time, 'seconds', 'rest']
try:
resarr += [interval['rest_distance']]
except KeyError: # pragma: no cover
resarr += [np.nan]
sa += iarr
results += resarr
if itime != 0:
ivelo = idist/itime
ipower = 2.8*ivelo**(3.0)
if workouttype in ['bike', 'bikeerg']: # pragma: no cover
ipower = 2.8*(ivelo/2.)**(3.0)
else: # pragma: no cover
ivelo = 0
ipower = 0
sums += interval_string(intervalnr, idist, itime, ipace, ispm,
iavghr, imaxhr, 0, ipower, separator=sep)
intervalnr += 1
return sums, sa, results
# Create the Data object for the stroke data to be sent to Concept2 logbook
# API
def createc2workoutdata(w):
filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except IOError: # pragma: no cover
return 0
try:
averagehr = int(row.df[' HRCur (bpm)'].mean())
maxhr = int(row.df[' HRCur (bpm)'].max())
except (ValueError, KeyError): # pragma: no cover
averagehr = 0
maxhr = 0
# Calculate intervalstats
itime, idist, itype = row.intervalstats_values()
try:
lapnames = row.df[' lapIdx'].unique()
except KeyError: # pragma: no cover
lapnames = range(len(itime))
nrintervals = len(itime)
if len(lapnames) != nrintervals:
newlapnames = []
for name in lapnames:
newlapnames += [name, name]
lapnames = newlapnames
intervaldata = []
for i in range(nrintervals):
if itime[i] > 0:
mask = (row.df[' lapIdx'] == lapnames[i]) & (
row.df[' WorkoutState'] == itype[i])
try:
spmav = int(row.df[' Cadence (stokes/min)']
[mask].mean().astype(int))
hrav = int(row.df[' HRCur (bpm)'][mask].mean().astype(int))
except AttributeError: # pragma: no cover
try:
spmav = int(row.df[' Cadence (stokes/min)'][mask].mean())
hrav = int(row.df[' HRCur (bpm)'][mask].mean())
except ValueError:
spmav = 0
try:
hrav = int(row.df[' HRCur (bpm)'][mask].mean())
except ValuError:
hrav = 0
intervaldict = {
'type': 'distance',
'time': int(10*itime[i]),
'distance': int(idist[i]),
'heart_rate': {
'average': hrav,
},
'stroke_rate': spmav,
}
intervaldata.append(intervaldict)
# adding diff, trying to see if this is valid
t = 10*row.df.loc[:, 'TimeStamp (sec)'].values - \
10*row.df.loc[:, 'TimeStamp (sec)'].iloc[0]
try:
t[0] = t[1]
except IndexError: # pragma: no cover
pass
d = 10*row.df.loc[:, ' Horizontal (meters)'].values
try:
d[0] = d[1]
except IndexError: # pragma: no cover
pass
p = abs(10*row.df.loc[:, ' Stroke500mPace (sec/500m)'].values)
p = np.clip(p, 0, 3600)
if w.workouttype == 'bike': # pragma: no cover
p = 2.0*p
t = t.astype(int)
d = d.astype(int)
p = p.astype(int)
spm = row.df[' Cadence (stokes/min)'].astype(int)
try:
spm[0] = spm[1]
except (KeyError, IndexError): # pragma: no cover
spm = 0*t
try:
hr = row.df[' HRCur (bpm)'].astype(int)
except ValueError: # pragma: no cover
hr = 0*d
stroke_data = []
t = t.tolist()
d = d.tolist()
p = p.tolist()
spm = spm.tolist()
hr = hr.tolist()
for i in range(len(t)):
thisrecord = {"t": t[i],
"d": d[i],
"p": p[i],
"spm": spm[i],
"hr": hr[i]}
stroke_data.append(thisrecord)
try:
durationstr = datetime.datetime.strptime(
str(w.duration), "%H:%M:%S.%f")
except ValueError:
durationstr = datetime.datetime.strptime(str(w.duration), "%H:%M:%S")
workouttype = w.workouttype
if workouttype in otwtypes:
workouttype = 'water'
if w.timezone == 'tzutc()': # pragma: no cover
w.timezone = 'UTC'
w.save()
try:
wendtime = w.startdatetime.astimezone(pytz.timezone(
w.timezone))+datetime.timedelta(seconds=makeseconds(durationstr))
except UnknownTimeZoneError:
wendtime = w.startdatetime.astimezone(pytz.utc)+datetime.timedelta(seconds=makeseconds(durationstr))
data = {
"type": mytypes.c2mapping[workouttype],
# w.startdatetime.isoformat(),
"date": wendtime.strftime('%Y-%m-%d %H:%M:%S'),
"stroke_count": int(row.stroke_count),
"timezone": w.timezone,
"distance": int(w.distance),
"time": int(10*makeseconds(durationstr)),
"weight_class": c2wc(w.weightcategory),
"comments": w.notes,
'stroke_rate': int(row.df[' Cadence (stokes/min)'].mean()),
'drag_factor': int(row.dragfactor),
"heart_rate": {
"average": averagehr,
"max": maxhr,
},
"stroke_data": stroke_data,
'workout': {
'splits': intervaldata,
}
}
return data
# Refresh Concept2 authorization token
def do_refresh_token(refreshtoken):
scope = "results:write,user:read"
# client_auth = requests.auth.HTTPBasicAuth(C2_CLIENT_ID, C2_CLIENT_SECRET)
post_data = {"grant_type": "refresh_token",
"client_secret": C2_CLIENT_SECRET,
"client_id": C2_CLIENT_ID,
"refresh_token": refreshtoken,
}
headers = {'user-agent': 'sanderroosendaal'}
url = "https://log.concept2.com/oauth/access_token"
s = Session()
req = Request('POST', url, data=post_data, headers=headers)
prepped = req.prepare()
prepped.body += "&scope="
prepped.body += scope
response = s.send(prepped)
try:
token_json = response.json()
except JSONDecodeError: # pragma: no cover
return [None, None, None]
try:
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
except: # pragma: no cover
with open("media/c2errors.log", "a") as errorlog:
errorstring = str(sys.exc_info()[0])
timestr = time.strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n")
errorlog.write(str(token_json)+"\r\n")
thetoken = None
expires_in = None
refresh_token = None
return [thetoken, expires_in, refresh_token]
# Exchange authorization code for authorization token
def get_token(code):
messg = ''
scope = "user:read,results:write"
# client_auth = requests.auth.HTTPBasicAuth(C2_CLIENT_ID, C2_CLIENT_SECRET)
post_data = {"grant_type": "authorization_code",
"code": code,
"redirect_uri": C2_REDIRECT_URI,
"client_secret": C2_CLIENT_SECRET,
"client_id": C2_CLIENT_ID,
}
headers = {'user-agent': 'sanderroosendaal'}
url = "https://log.concept2.com/oauth/access_token"
s = Session()
req = Request('POST', url, data=post_data, headers=headers)
prepped = req.prepare()
prepped.body += "&scope="
prepped.body += scope
response = s.send(prepped)
token_json = response.json()
try:
status_code = response.status_code
# status_code = token_json['status_code']
except AttributeError: # pragma: no cover
# except KeyError:
return (0, response.text)
try:
status_code = token_json.status_code
except AttributeError: # pragma: no cover
return (0, 'Attribute Error on c2_get_token')
if status_code == 200:
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
else: # pragma: no cover
return (0, token_json['message'])
return (thetoken, expires_in, refresh_token, messg)
# Make URL for authorization and load it
def make_authorization_url(request): # pragma: no cover
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
# state = str(uuid4())
scope = "user:read,results:write"
params = {"client_id": C2_CLIENT_ID,
"response_type": "code",
"redirect_uri": C2_REDIRECT_URI}
url = "https://log.concept2.com/oauth/authorize?" + \
urllib.parse.urlencode(params)
url += "&scope="+scope
return HttpResponseRedirect(url)
# Get workout from C2 ID
def get_workout(user, c2id, do_async=True):
r = Rower.objects.get(user=user)
_ = c2_open(user)
_ = myqueue(queuehigh,
handle_c2_getworkout,
user.id,
r.c2token,
c2id,
r.defaulttimezone)
return 1
# Get list of C2 workouts. We load only the first page,
# assuming that users don't want to import their old workouts
def get_c2_workout_list(user, page=1):
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)
elif (timezone.now() > r.tokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh."
return custom_exception_handler(401, s)
# 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"
url += "?page={page}".format(page=page)
s = requests.get(url, headers=headers)
return s
# Get username, having access token.
# Handy for checking if the API access is working
def get_username(access_token): # pragma: no cover
authorizationstring = str('Bearer ' + access_token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "https://log.concept2.com/api/users/me"
response = requests.get(url, headers=headers)
me_json = response.json()
try:
res = me_json['data']['username']
_ = me_json['data']['id']
except KeyError:
res = None
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://log.concept2.com/api/users/me"
try:
response = requests.get(url, headers=headers)
except: # pragma: no cover
return 0
try:
me_json = response.json()
except: # pragma: no cover
return 0
try:
res = me_json['data']['id']
except KeyError: # pragma: no cover
return 0
return res
# For debugging purposes
def process_callback(request): # pragma: no cover
# need error handling
code = request.GET['code']
access_token = get_token(code)
username, id = get_username(access_token)
return HttpResponse("got a user name: %s" % username)
def default(o): # pragma: no cover
if isinstance(o, numpy.int64):
return int(o)
raise TypeError
# 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
except KeyError: # pragma: no cover
return "This workout type cannot be uploaded to Concept2", 0
_ = c2_open(user)
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('debuglog.log',
'Upload to C2 user {userid}'.format(userid=user.id))
data = createc2workoutdata(w)
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
authorizationstring = str('Bearer ' + r.c2token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "https://log.concept2.com/api/users/%s/results" % (c2userid)
if not asynchron: # pragma: no cover
response = requests.post(
url, headers=headers, data=json.dumps(data, default=default))
if (response.status_code == 409): # pragma: no cover
message = "Concept2 Duplicate error"
w.uploadedtoc2 = -1
c2id = -1
w.save()
elif (response.status_code == 201 or response.status_code == 200):
# s= json.loads(response.text)
s = response.json()
c2id = s['data']['id']
w.uploadedtoc2 = c2id
w.save()
message = "Upload to Concept2 was successful"
else: # pragma: no cover
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
_ = myqueue(queue,
handle_c2_sync,
w.id,
url,
headers,
json.dumps(data, default=default))
c2id = 0
return message, c2id
# This is token refresh. Looks for tokens in our database, then refreshes
def rower_c2_token_refresh(user):
r = Rower.objects.get(user=user)
res = do_refresh_token(r.c2refreshtoken)
if res[0]:
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = Rower.objects.get(user=user)
r.c2token = access_token
r.tokenexpirydate = expirydatetime
r.c2refreshtoken = refresh_token
r.save()
return r.c2token
else: # pragma: no cover
return None
+3 -3
View File
@@ -66,7 +66,7 @@ def splitstdata(lijst):
return [np.array(t), np.array(latlong)]
# covered in integrations
def imports_open(user, oauth_data):
r = Rower.objects.get(user=user)
token = getattr(r, oauth_data['tokenname'])
@@ -101,7 +101,7 @@ def imports_open(user, oauth_data):
return token
# covered in integrations
# Refresh token using refresh token
def imports_do_refresh_token(refreshtoken, oauth_data, access_token=''):
# client_auth = requests.auth.HTTPBasicAuth(
@@ -175,7 +175,7 @@ def imports_do_refresh_token(refreshtoken, oauth_data, access_token=''):
# Exchange ST access code for long-lived ST access token
# implemented in integrations
def imports_get_token(
code, oauth_data
):
+19
View File
@@ -0,0 +1,19 @@
from .c2 import C2Integration
from .strava import StravaIntegration
from .nk import NKIntegration
from .sporttracks import SportTracksIntegration
from .rp3 import RP3Integration
from .trainingpeaks import TPIntegration
from .polar import PolarIntegration
importsources = {
'c2': C2Integration,
'strava': StravaIntegration,
'sporttracks': SportTracksIntegration,
'trainingpeaks': TPIntegration,
'nk': NKIntegration,
'tp':TPIntegration,
'rp3':RP3Integration,
'polar': PolarIntegration
}
+453
View File
@@ -0,0 +1,453 @@
from .integrations import SyncIntegration, NoTokenError
from rowers.models import User, Rower, Workout, TombStone
from rowingdata import rowingdata
import numpy as np
import datetime
import json
import urllib
from rowers.utils import dologging, uniqify, custom_exception_handler
from django.utils import timezone
import requests
from pytz.exceptions import UnknownTimeZoneError
import pytz
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET
)
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')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
from rowers.utils import myqueue, dologging
from requests import Request, Session
import rowers.mytypes as mytypes
from rowers.rower_rules import is_workout_user, ispromember
def default(o): # pragma: no cover
if isinstance(o, numpy.int64):
return int(o)
raise TypeError
# convert datetime object to seconds
def makeseconds(t):
seconds = t.hour*3600.+t.minute*60.+t.second+0.1*int(t.microsecond/1.e5)
return seconds
# convert our weight class code to Concept2 weight class code
def c2wc(weightclass):
if (weightclass == "lwt"): # pragma: no cover
res = "L"
else:
res = "H"
return res
class C2Integration(SyncIntegration):
def __init__(self, *args, **kwargs):
super(C2Integration, self).__init__(*args, **kwargs)
self.oauth_data = {
'client_id': C2_CLIENT_ID,
'client_secret': C2_CLIENT_SECRET,
'redirect_uri': C2_REDIRECT_URI,
'autorization_uri': "https://log.concept2.com/oauth/authorize",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'c2token',
'refreshtokenname': 'c2refreshtoken',
'expirydatename': 'tokenexpirydate',
'bearer_auth': True,
'base_url': "https://log.concept2.com/oauth/access_token",
'scope': 'write',
}
def get_name(self):
return "Concept2 Logbook"
def get_shortname(self):
return "c2"
def get_token(self, code, *args, **kwargs):
messg = ''
scope = "user:read,results:write"
post_data = {"grant_type": "authorization_code",
"code": code,
"redirect_uri": C2_REDIRECT_URI,
"client_secret": C2_CLIENT_SECRET,
"client_id": C2_CLIENT_ID,
}
headers = {'user-agent': 'sanderroosendaal'}
url = "https://log.concept2.com/oauth/access_token"
s = Session()
req = Request('POST', url, data=post_data, headers=headers)
prepped = req.prepare()
prepped.body += "&scope="
prepped.body += scope
response = s.send(prepped)
token_json = response.json()
try:
status_code = response.status_code
except AttributeError: # pragma: no cover
return (0, response.text)
if status_code == 200:
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
else: # pragma: no cover
raise NoTokenError("No Token")
return (thetoken, expires_in, refresh_token, messg)
def open(self, *args, **kwargs):
return super(C2Integration, self).open(*args, **kwargs)
def token_refresh(self, *args, **kwargs):
return super(C2Integration, self).token_refresh(*args, **kwargs)
def make_authorization_url(self, *args, **kwargs):
scope = "user:read,results:write"
params = {"client_id": C2_CLIENT_ID,
"response_type": "code",
"redirect_uri": C2_REDIRECT_URI}
url = "https://log.concept2.com/oauth/authorize?" + \
urllib.parse.urlencode(params)
url += "&scope="+scope
return url
def get_userid(self, *args, **kwargs):
_ = self.open()
access_token = self.rower.c2token
authorizationstring = str('Bearer ' + access_token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://log.concept2.com/api/users/me"
try:
response = requests.get(url, headers=headers)
except: # pragma: no cover
return 0
try:
me_json = response.json()
except: # pragma: no cover
return 0
try:
res = me_json['data']['id']
except KeyError: # pragma: no cover
return 0
return res
def createworkoutdata(self, w, *args, **kwargs) -> dict:
filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except IOError: # pragma: no cover
return 0
try:
averagehr = int(row.df[' HRCur (bpm)'].mean())
maxhr = int(row.df[' HRCur (bpm)'].max())
except (ValueError, KeyError): # pragma: no cover
averagehr = 0
maxhr = 0
# Calculate intervalstats
itime, idist, itype = row.intervalstats_values()
lapnames = range(len(itime))
nrintervals = len(itime)
try:
lapnames = row.df[' lapIdx'].unique()
except KeyError: # pragma: no cover
pass
if len(lapnames) != nrintervals:
newlapnames = []
for name in lapnames:
newlapnames += [name, name]
lapnames = newlapnames
intervaldata = []
for i in range(nrintervals):
if itime[i] > 0:
mask = (row.df[' lapIdx'] == lapnames[i]) & (
row.df[' WorkoutState'] == itype[i])
try:
spmav = int(row.df[' Cadence (stokes/min)']
[mask].mean().astype(int))
hrav = int(row.df[' HRCur (bpm)'][mask].mean().astype(int))
except AttributeError: # pragma: no cover
try:
spmav = int(row.df[' Cadence (stokes/min)'][mask].mean())
hrav = int(row.df[' HRCur (bpm)'][mask].mean())
except ValueError:
spmav = 0
try:
hrav = int(row.df[' HRCur (bpm)'][mask].mean())
except ValuError:
hrav = 0
intervaldict = {
'type': 'distance',
'time': int(10*itime[i]),
'distance': int(idist[i]),
'heart_rate': {
'average': hrav,
},
'stroke_rate': spmav,
}
intervaldata.append(intervaldict)
# adding diff, trying to see if this is valid
t = 10*row.df.loc[:, 'TimeStamp (sec)'].values - \
10*row.df.loc[:, 'TimeStamp (sec)'].iloc[0]
try:
t[0] = t[1]
except IndexError: # pragma: no cover
pass
d = 10*row.df.loc[:, ' Horizontal (meters)'].values
try:
d[0] = d[1]
except IndexError: # pragma: no cover
pass
p = abs(10*row.df.loc[:, ' Stroke500mPace (sec/500m)'].values)
p = np.clip(p, 0, 3600)
if w.workouttype == 'bike': # pragma: no cover
p = 2.0*p
t = t.astype(int)
d = d.astype(int)
p = p.astype(int)
spm = row.df[' Cadence (stokes/min)'].astype(int)
try:
spm[0] = spm[1]
except (KeyError, IndexError): # pragma: no cover
spm = 0*t
try:
hr = row.df[' HRCur (bpm)'].astype(int)
except ValueError: # pragma: no cover
hr = 0*d
stroke_data = []
t = t.tolist()
d = d.tolist()
p = p.tolist()
spm = spm.tolist()
hr = hr.tolist()
for i in range(len(t)):
thisrecord = {"t": t[i],
"d": d[i],
"p": p[i],
"spm": spm[i],
"hr": hr[i]}
stroke_data.append(thisrecord)
try:
durationstr = datetime.datetime.strptime(
str(w.duration), "%H:%M:%S.%f")
except ValueError:
durationstr = datetime.datetime.strptime(str(w.duration), "%H:%M:%S")
workouttype = w.workouttype
if workouttype in mytypes.otwtypes:
workouttype = 'water'
if w.timezone == 'tzutc()': # pragma: no cover
w.timezone = 'UTC'
w.save()
try:
wendtime = w.startdatetime.astimezone(pytz.timezone(
w.timezone))+datetime.timedelta(seconds=makeseconds(durationstr))
except UnknownTimeZoneError:
wendtime = w.startdatetime.astimezone(pytz.utc)+datetime.timedelta(seconds=makeseconds(durationstr))
data = {
"type": mytypes.c2mapping[workouttype],
# w.startdatetime.isoformat(),
"date": wendtime.strftime('%Y-%m-%d %H:%M:%S'),
"stroke_count": int(row.stroke_count),
"timezone": w.timezone,
"distance": int(w.distance),
"time": int(10*makeseconds(durationstr)),
"weight_class": c2wc(w.weightcategory),
"comments": w.notes,
'stroke_rate': int(row.df[' Cadence (stokes/min)'].mean()),
'drag_factor': int(row.dragfactor),
"heart_rate": {
"average": averagehr,
"max": maxhr,
},
"stroke_data": stroke_data,
'workout': {
'splits': intervaldata,
}
}
return data
def workout_export(self, workout, *args, **kwargs) -> str:
try:
if mytypes.c2mapping[workout.workouttype] is None: # pragma: no cover
return "0"
except KeyError: # pragma: no cover
return "0"
_ = self.open()
r = self.rower
user = self.user
if (is_workout_user(user, workout)):
c2userid = self.get_userid()
if not c2userid: # pragma: no cover
raise NoTokenError("User has no token")
dologging('debuglog.log',
'Upload to C2 user {userid}'.format(userid=user.id))
data = self.createworkoutdata(workout)
dologging('c2_log.log', json.dumps(data))
if data == 0: # pragma: no cover
return 0
authorizationstring = str('Bearer ' + r.c2token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://log.concept2.com/api/users/%s/results" % (c2userid)
_ = myqueue(queue,
handle_c2_sync,
workout.id,
url,
headers,
json.dumps(data, default=default))
c2id = 0
return c2id
def get_workout(self, id):
_ = self.open()
_ = myqueue(queuehigh,
handle_c2_getworkout,
self.user.id,
self.rower.c2token,
id,
self.rower.defaulttimezone)
return 1
def get_workouts(self, *args, **kwargs):
page = kwargs.get('page',1)
try:
_ = self.open()
except NoTokenError:
return 0
# this part to get_workout_list
workouts = self.get_workout_list(page=page)
for workout in workouts:
c2id = workout['id']
if workout['new'] == 'NEW':
self.get_workout(c2id)
return 1
# should be unified to workout list
def get_workout_list(self, *args, **kwargs):
page = kwargs.get('page',1)
r = self.rower
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)
elif (timezone.now() > r.tokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh."
return custom_exception_handler(401, s)
# 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"
url += "?page={page}".format(page=page)
res = requests.get(url, headers=headers)
if (res.status_code != 200): # pragma: no cover
return []
workouts = []
c2ids = [item['id'] for item in res.json()['data']]
knownc2ids = uniqify([
w.uploadedtoc2 for w in Workout.objects.filter(user=self.rower)
])
tombstones = [
t.uploadedtoc2 for t in TombStone.objects.filter(user=self.rower)
]
parkedids = []
try:
with open('c2blocked.json', 'r') as c2blocked:
jsondata = json.load(c2blocked)
parkedids = jsondata['ids']
except: # pragma: no cover
pass
knownc2ids = uniqify(knownc2ids+tombstones+parkedids)
for item in res.json()['data']:
d = item['distance']
i = item['id']
ttot = item['time_formatted']
s = item['date']
r = item['type']
s2 = item['source']
c = item['comments']
if i in knownc2ids:
nnn = ''
else: # pragma: no cover
nnn = 'NEW'
keys = ['id', 'distance', 'duration', 'starttime',
'rowtype', 'source', 'name', 'new']
values = [i, d, ttot, s, r, s2, c, nnn]
ress = dict(zip(keys, values))
workouts.append(ress)
return workouts
+263
View File
@@ -0,0 +1,263 @@
from abc import ABCMeta, ABC, abstractmethod
from importlib import import_module
from rowers.models import Rower, User
from rowers.utils import NoTokenError
import requests
from django.utils import timezone
from datetime import timedelta
import arrow
import urllib
from uuid import uuid4
import json
class SyncIntegration(metaclass=ABCMeta):
oauth_data = {
'tokenname':'token',
'expirydatename':'exp',
'refreshtokenname':'r',
'redirect_uri': 'r',
'client_secret': 's',
'base_uri': 's'
}
user = User()
rower = Rower()
def __init__(self, *args, **kwargs):
user = args[0]
self.user = user
self.rower = user.rower
@classmethod
def __subclasshook__(cls, subclass):
return (hasattr(subclass, 'get_token') and
callable(subclass.get_token) or
NotImplemented)
@abstractmethod
def get_name(self):
raise NotImplementedError
@abstractmethod
def get_shortname(self):
raise NotImplementedError
@abstractmethod
def createworkoutdata(self, w, *args, **kwargs):
return None
@abstractmethod
def workout_export(self, workout, *args, **kwargs) -> str:
pass
@abstractmethod
def get_workouts(self, *args, **kwargs) -> int:
pass
@abstractmethod
def get_workout(self, id) -> int:
return 0
# need to unify workout list
@abstractmethod
def get_workout_list(self, *args, **kwargs) -> list:
return []
@abstractmethod
def make_authorization_url(self, *args, **kwargs) -> str: # 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": self.oauth_data['client_id'],
"response_type": "code",
"redirect_uri": self.oauth_data['redirect_uri'],
"scope": self.oauth_data['scope'],
"state": state}
url = self.oauth_data['authorization_uri']+urllib.parse.urlencode(params)
return url
@abstractmethod
def get_token(self, code, *args, **kwargs) -> (str, int, str):
redirect_uri = self.oauth_data['redirect_uri']
client_secret = self.oauth_data['client_secret']
client_id = self.oauth_data['client_id']
base_uri = self.oauth_data['base_url']
post_data = {"grant_type": "authorization_code",
"code": code,
"redirect_uri": redirect_uri,
"client_secret": client_secret,
"client_id": client_id,
}
try:
headers = self.oauth_data['headers']
except KeyError:
headers = {'Accept': 'application/json',
'Api-Key': client_id,
'Content-Type': 'application/json',
'user-agent': 'sanderroosendaal'}
if 'grant_type' in self.oauth_data:
if self.oauth_data['grant_type']:
post_data['grant_type'] = self.oauth_data['grant_type']
if 'strava' in self.oauth_data['autorization_uri']:
post_data['grant_type'] = "authorization_code"
if 'json' in self.oauth_data['content_type']:
response = requests.post(
base_uri,
data=json.dumps(post_data),
headers=headers)
else: # pragma: no cover
response = requests.post(
base_uri,
data=post_data,
headers=headers, verify=False)
if response.status_code == 200 or response.status_code == 201:
token_json = response.json()
try:
thetoken = token_json['access_token']
except KeyError: # pragma: no cover
raise NoTokenError("Failed to obtain token")
try:
refresh_token = token_json['refresh_token']
except KeyError: # pragma: no cover
refresh_token = ''
try:
expires_in = token_json['expires_in']
except KeyError: # pragma: no cover
try:
expires_at = arrow.get(token_json['expires_at']).timestamp()
expires_in = expires_at - arrow.now().timestamp()
except KeyError: # pragma: no cover
expires_in = 0
try:
expires_in = int(expires_in)
except (ValueError, TypeError): # pragma: no cover
expires_in = 0
else: # pragma: no cover
raise NoTokenError("Failed to obtain token")
return [thetoken, expires_in, refresh_token]
@abstractmethod
def open(self, *args, **kwargs) -> str:
token = getattr(self.rower, self.oauth_data['tokenname'])
try:
tokenexpirydate = getattr(self.rower, self.oauth_data['expirydatename'])
except (TypeError, AttributeError, KeyError): # pragma: no cover
tokenexpirydate = None
if (token == '') or (token is None):
raise NoTokenError("User has no token")
else:
tokenname = self.oauth_data['tokenname']
refreshtokenname = self.oauth_data['refreshtokenname']
expirydatename = self.oauth_data['expirydatename']
if tokenexpirydate and timezone.now()+timedelta(seconds=60) > tokenexpirydate:
token = self.token_refresh()
elif tokenexpirydate is None and expirydatename is not None and 'strava' in expirydatename: # pragma: no cover
token = self.token_refresh()
return token
def do_refresh_token(self, *args, **kwargs) -> (str, int, str):
refreshtoken = getattr(self.rower, self.oauth_data['refreshtokenname'])
access_token = kwargs.get('access_token','')
post_data = {"grant_type": "refresh_token",
"client_secret": self.oauth_data['client_secret'],
"client_id": self.oauth_data['client_id'],
"refresh_token": refreshtoken,
}
headers = {'user-agent': 'sanderroosendaal',
'Accept': 'application/json',
'Content-Type': self.oauth_data['content_type']}
# for Strava
if 'grant_type' in self.oauth_data:
if self.oauth_data['grant_type']:
post_data['grant_type'] = self.oauth_data['grant_type']
if self.oauth_data['bearer_auth']:
headers['authorization'] = 'Bearer %s' % access_token
baseurl = self.oauth_data['base_url']
if 'json' in self.oauth_data['content_type']:
try:
response = requests.post(baseurl,
data=json.dumps(post_data),
headers=headers, verify=False)
except: # pragma: no cover
raise NoTokenError("Failed to get token")
else:
try:
response = requests.post(baseurl,
data=post_data,
headers=headers, verify=False,
)
except: # pragma: no cover
raise NoTokenError("Failed to get token")
if response.status_code == 200 or response.status_code == 201:
token_json = response.json()
else: # pragma: no cover
raise NoTokenError("User has no token")
try:
thetoken = token_json['access_token']
except KeyError: # pragma: no cover
raise NoTokenError("User has no token")
try:
expires_in = token_json['expires_in']
except KeyError:
try:
expires_at = arrow.get(token_json['expires_at']).timestamp()
expires_in = expires_at - arrow.now().timestamp()
except KeyError: # pragma: no cover
expires_in = 0
try:
refresh_token = token_json['refresh_token']
except KeyError: # pragma: no cover
refresh_token = refreshtoken
try:
expires_in = int(expires_in)
except (TypeError, ValueError): # pragma: no cover
expires_in = 0
return [thetoken, expires_in, refresh_token]
@abstractmethod
def token_refresh(self, *args, **kwargs) -> str:
refreshtoken = getattr(self.rower, self.oauth_data['refreshtokenname'])
if not refreshtoken:
refreshtoken = getattr(self.rower, self.oauth_data['tokenname'])
access_token, expires_in, refresh_token = self.do_refresh_token()
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
setattr(self.rower, self.oauth_data['tokenname'], access_token)
if self.oauth_data['expirydatename'] is not None:
setattr(self.rower, self.oauth_data['expirydatename'], expirydatetime)
if self.oauth_data['refreshtokenname'] is not None:
setattr(self.rower, self.oauth_data['refreshtokenname'], refresh_token)
self.rower.save()
return access_token
+316
View File
@@ -0,0 +1,316 @@
from .integrations import SyncIntegration, NoTokenError
from rowers.models import User, Rower, Workout, TombStone
from rowers import mytypes
from rowers.nkimportutils import *
from rowers.tasks import handle_nk_async_workout
from rowsandall_app.settings import (
NK_CLIENT_ID, NK_REDIRECT_URI, NK_CLIENT_SECRET,
SITE_URL, NK_API_LOCATION, NK_OAUTH_LOCATION,
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET,
)
import time
from time import strftime
import urllib
import requests
import arrow
from json.decoder import JSONDecodeError
from django.utils import timezone
from rowers.utils import dologging, uniqify, custom_exception_handler, myqueue
from requests_oauthlib import OAuth2Session
from requests.auth import HTTPBasicAuth
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
class NKIntegration(SyncIntegration):
def __init__(self, *args, **kwargs):
super(NKIntegration, self).__init__(*args, **kwargs)
self.oauth_data = {
'client_id': NK_CLIENT_ID,
'client_secret': NK_CLIENT_SECRET,
'redirect_uri': NK_REDIRECT_URI,
'autorization_uri': NK_OAUTH_LOCATION+"/oauth/authorize",
'content_type': 'application/json',
'tokenname': 'nktoken',
'refreshtokenname': 'nkrefreshtoken',
'expirydatename': 'nktokenexpirydate',
'bearer_auth': True,
'base_url': NK_OAUTH_LOCATION+"/oauth/token",
'scope': 'read',
}
def get_name(self):
return "NK Logbook"
def get_shortname(self):
return "nk"
def createworkoutdata(self, w, *args, **kwargs):
return None
def workout_export(self, workout, *args, **kwargs) -> str:
return "" # there is no export
def get_workouts(self, *args, **kwargs) -> int:
before = kwargs.get('before',0)
after = kwargs.get('after',0)
try:
_ = self.open()
except NoTokenError: # pragma: no cover
dologging("nklog.log","NK Token error for user {id}".format(id=rower.user.id))
return 0
workouts = self.get_workout_list(before=before, after=after)
for workout in workouts:
nkid = workout['id']
if workout['new'] == 'NEW':
dologging('nklog.log','Queueing {id}'.format(id=nkid))
self.get_workout(nkid)
return 1
def get_workout(self, id, *args, **kwargs) -> int:
startdate = kwargs.get('startdate','')
enddate = kwargs.get('enddate','')
_ = self.open()
r = self.rower
before = 0
after = 0
if startdate: # pragma: no cover
startdate = arrow.get(startdate)
after = str(int(startdate.timestamp())*1000)
if enddate: # pragma: no cover
enddate = arrow.get(enddate)
before = str(int(enddate.timestamp())*1000)
jsondata = self.get_workout_list_json(before=before, after=after)
alldata = {}
for item in jsondata:
alldata[item['id']] = item
res = myqueue(
queuehigh,
handle_nk_async_workout,
alldata,
r.user.id,
r.nktoken,
id,
0,
r.defaulttimezone,
)
return 1
def get_workout_list_json(self, *args, **kwargs) -> dict:
before = kwargs.get('before',0)
after = kwargs.get('after',0)
# For debugging
#startdate = '2021-01-01'
#enddate = '2021-06-01'
#before = arrow.get(enddate)
#before = str(int(before.timestamp()*1000))
#after = arrow.get(startdate)
#after = str(int(after.timestamp()*1000))
r = self.rower
authorizationstring = str('Bearer ' + r.nktoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json',
}
url = NK_API_LOCATION+"api/v1/sessions"
params = {
'after': after,
'before': before,
} # start / end time
res = requests.get(url, headers=headers, params=params)
if (res.status_code != 200): # pragma: no cover
raise NoTokenError("No NK Token")
return res.json()
# need to unify workout list
def get_workout_list(self, *args, **kwargs) -> list:
_ = self.open()
r = self.rower
before = kwargs.get('before',0)
after = kwargs.get('after',0)
# ready to fetch. Hurray
if not before: # pragma: no cover
before = arrow.now()+timedelta(days=1)
before = str(int(before.timestamp())*1000)
if not after: # pragma: no cover
after = arrow.now()-timedelta(days=7)
after = str(int(after.timestamp())*1000)
jsondata = self.get_workout_list_json(before=before,after=after)
# get NK IDs
nkids = [item['id'] for item in jsondata]
knownnkids = uniqify([
w.uploadedtonk for w in Workout.objects.filter(user=r)
])
tombstones = [
t.uploadedtonk for t in TombStone.objects.filter(user=r)
]
parkedids = []
try:
with open('nkblocked.json', 'r') as nkblocked:
try:
jsondatal = json.load(nkblocked)
except:
jsondatal = {
'ids':[]
}
parkedids = jsondatal['ids']
except FileNotFoundError: # pragma: no cover
pass
knownnkids = uniqify(knownnkids+tombstones+parkedids)
workouts = []
for item in jsondata:
d = int(float(item['totalDistanceGps'])) # could also be Impeller
i = item['id']
n = item['name']
if i in knownnkids:
nnn = ''
else: # pragma: no cover
nnn = 'NEW'
ttot = str(datetime.timedelta(
seconds=int(float(item['elapsedTime'])/1000.)))
s = arrow.get(item['startTime'], tzinfo=r.defaulttimezone).format(
arrow.FORMAT_RFC850)
keys = ['id', 'distance', 'duration', 'starttime',
'rowtype', 'source', 'name','new']
values = [i, d, ttot, s, None, None, n, nnn]
rs = dict(zip(keys, values))
workouts.append(rs)
workouts = workouts[::-1]
return workouts
def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover
state = str(uuid4())
scope = "read"
params = {
"grant_type": "authorization_code",
"response_type": "code",
"client_id": NK_CLIENT_ID,
"scope": scope,
"state": state,
"redirect_uri": NK_REDIRECT_URI,
}
url = NK_OAUTH_LOCATION+"/oauth/authorize?"+urllib.parse.urlencode(params)
return url
def get_token(self, code, *args, **kwargs) -> (str, int, str):
url = self.oauth_data['base_url']
post_data = {"client_id": self.oauth_data['client_id'],
"grant_type": "authorization_code",
"redirect_uri": self.oauth_data['redirect_uri'],
"code": code,
}
response = requests.post(
url,
auth=HTTPBasicAuth(self.oauth_data['client_id'],
self.oauth_data['client_secret']),
data=post_data
)
if response.status_code != 200:
raise NoTokenError("Failed to obtain token")
token_json = response.json()
access_token = token_json['access_token']
refresh_token = token_json['refresh_token']
expires_in = token_json['expires_in']
nk_owner_id = token_json['user_id']
return [access_token, expires_in, refresh_token] #, nk_owner_id]
def open(self, *args, **kwargs) -> str:
r = self.rower
if (r.nktoken == '') or (r.nktoken is None): # pragma: no cover
raise NoTokenError("User has no token")
else:
if (timezone.now() > r.nktokenexpirydate):
thetoken = self.token_refresh()
if thetoken is None: # pragma: no cover
raise NoTokenError("User has no token")
return thetoken
else:
thetoken = r.nktoken
return thetoken
def do_refresh_token(self, *args, **kwargs):
post_data = {"grant_type": "refresh_token",
# "client_id":NK_CLIENT_ID,
"refresh_token": self.rower.nkrefreshtoken,
}
url = self.oauth_data['base_url']
response = requests.post(
url,
data=post_data,
auth=HTTPBasicAuth(
self.oauth_data['client_id'],
self.oauth_data['client_secret']
)
)
if response.status_code != 200: # pragma: no cover
return [0, 0, 0]
token_json = response.json()
access_token = token_json['access_token']
refresh_token = token_json['refresh_token']
expires_in = token_json['expires_in']
return access_token, expires_in, refresh_token
def token_refresh(self, *args, **kwargs) -> str:
r = self.rower
access_token, expires_in, refresh_token = self.do_refresh_token()
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
r.nktoken = access_token
r.nktokenexpirydate = expirydatetime
r.nkrefreshtoken = refresh_token
r.save()
return r.nktoken
+521
View File
@@ -0,0 +1,521 @@
from rowers.rower_rules import ispromember
from .integrations import SyncIntegration
from rowers.models import User, Rower, Workout
from rowsandall_app.settings import (
POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET, UPLOAD_SERVICE_URL
)
import urllib
import requests
from rowers.utils import dologging, myqueue, NoTokenError
from django.utils import timezone
from uuid import uuid4
import base64
from rowers.tasks import handle_request_post
from json.decoder import JSONDecodeError
from rowers.opaque import encoder
import rowers.mytypes as mytypes
from django.conf import settings
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
baseurl = 'https://polaraccesslink.com/v3'
class PolarIntegration(SyncIntegration):
def __init__(self, *args, **kwargs):
if args[0] is not None:
super(PolarIntegration, self).__init__(*args, **kwargs)
def get_notifications(self):
url = baseurl+'/notifications'
# state = str(uuid4())
auth_string = '{id}:{secret}'.format(
id=POLAR_CLIENT_ID,
secret=POLAR_CLIENT_SECRET
)
try:
headers = {'Authorization': 'Basic %s' % base64.b64encode(auth_string)}
except TypeError:
headers = {'Authorization': 'Basic %s' % base64.b64encode(
bytes(auth_string, 'utf-8')).decode('utf-8')}
try:
response = requests.get(url, headers=headers)
except ConnectionError: # pragma: no cover
response = {
'status_code': 400,
}
available_data = []
try:
if response.status_code == 200:
available_data = response.json()['available-user-data']
dologging('polar.log', available_data)
else: # pragma: no cover
dologging('polar.log', response.status_code)
dologging('polar.log', response.text)
except AttributeError: # pragma: no cover
try:
dologging('polar.log', response.text)
except AttributeError:
pass
pass
return available_data
def get_name(self):
return "Polar Flow"
def get_shortname(self):
raise "polar"
def createworkoutdata(self, w, *args, **kwargs):
raise NotImplementedError
def workout_export(self, workout, *args, **kwargs) -> str:
raise NotImplementedError
def revoke_access(self): # pragma: no cover
user = self.user
headers = {
'Authorization': 'Bearer {token}'.format(token=user.rower.polartoken)
}
response = requests.delete('https://www.polaraccesslink.com/v3/users/{userid}'.format(
userid=user.rower.polaruserid
), headers=headers)
dologging('polar.log', response.text)
dologging('polar.log', response.reason)
return 1
def get_polar_workouts(self, user):
r = Rower.objects.get(user=user)
exercise_list = []
if (r.polartoken == '') or (r.polartoken is None):
s = "Token doesn't exist. Need to authorize"
return []
elif (timezone.now() > r.polartokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh"
dologging('polar.log', s)
return []
authorizationstring = str('Bearer ' + r.polartoken)
headers = {'Authorization': authorizationstring,
'Accept': 'application/json'}
headers2 = {
'Authorization': authorizationstring,
}
url = baseurl+'/users/{userid}/exercise-transactions'.format(
userid=r.polaruserid
)
response = requests.post(url, headers=headers)
dologging('polar.log', url)
dologging('polar.log', authorizationstring)
dologging('polar.log', str(response.status_code))
if response.status_code == 201:
transactionid = response.json()['transaction-id']
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
dologging('polar.log', url)
response = requests.get(url, headers=headers)
if response.status_code == 200:
exerciseurls = response.json()['exercises']
dologging('polar.log', exerciseurls)
for exerciseurl in exerciseurls:
response = requests.get(exerciseurl, headers=headers)
if response.status_code == 200:
exercise_dict = response.json()
tcxuri = exerciseurl+'/tcx'
response = requests.get(tcxuri, headers=headers2)
if response.status_code == 200:
filename = 'media/mailbox_attachments/{code}_{id}.tcx'.format(
id=exercise_dict['id'],
code=uuid4().hex[:16]
)
dologging('polar.log', filename)
with open(filename, 'wb') as fop:
fop.write(response.content)
workouttype = 'other'
try:
workouttype = mytypes.polaraccesslink_sports[
exercise_dict['detailed-sport-info']]
except KeyError: # pragma: no cover
dologging(
'polar.log', exercise_dict['detailed-sport-info'])
dologging('polar.log', workouttype)
try:
workouttype = mytypes.polarmappinginv[exercise_dict['sport'].lower(
)]
except KeyError:
dologging('polar.log', workouttype)
pass
dologging('polar.log', workouttype)
# post file to upload api
# TODO: add workouttype
uploadoptions = {
'title': '',
'workouttype': workouttype,
'boattype': '1x',
'user': user.id,
'secret': settings.UPLOAD_SERVICE_SECRET,
'file': filename,
'title': '',
}
url = settings.UPLOAD_SERVICE_URL
dologging('polar.log', uploadoptions)
dologging('polar.log', url)
_ = myqueue(
queuehigh,
handle_request_post,
url,
uploadoptions
)
dologging('polar.log', response.status_code)
if response.status_code != 200: # pragma: no cover
try:
dologging('polar.log', response.text)
except:
pass
try:
dologging('polar.log', response.json())
except:
pass
exercise_dict['filename'] = filename
else: # pragma: no cover
exercise_dict['filename'] = ''
exercise_list.append(exercise_dict)
dologging('polar.log', str(exercise_dict))
# commit transaction
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
requests.put(url, headers=headers)
dologging(
'polar.log', 'Committed transation at {url}'.format(url=url))
return exercise_list
def get_workouts(self, *args, **kwargs) -> int:
available_data = self.get_notifications()
polaruserid = self.rower.polaruserid
for record in available_data:
dologging('polar.log', str(record))
if record['data-type'] == 'EXERCISE':
try:
r = Rower.objects.get(polaruserid=record['user-id'])
u = r.user
if r.polar_auto_import and ispromember(u):
exercise_list = self.get_polar_workouts(u)
dologging('polar.log', exercise_list)
elif record['user-id'] == polaruserid:
exercise_list = self.get_polar_workouts(u)
except Rower.DoesNotExist: # pragma: no cover
pass
return 1
def register_user(self, token):
_ = self.open()
authorizationstring = 'Bearer {token}'.format(token=token)
headers = {
'Content-Type': 'application/xml',
'Authorization': authorizationstring,
'Accept': 'application/json'
}
payload = {
"member-id": encoder.encode_hex(self.user.id)
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer {token}'.format(token=token)
}
dologging('polar.log', 'Registering user')
response = requests.post(
'https://www.polaraccesslink.com/v3/users',
json=payload,
headers=headers
)
if response.status_code not in [200, 201]: # pragma: no cover
# dologging('polar.log',url)
dologging('polar.log', headers)
dologging('polar.log', payload)
dologging('polar.log', response.status_code)
dologging('polar.log', response.content)
try:
dologging('polar.log', response.reason)
dologging('polar.log', response.text)
except KeyError:
pass
try:
jsondata = response.json()
if jsondata['error']['error_type'] == 'user_already_registered':
return jsondata
except:
pass
return {}
polar_user_data = response.json()
return polar_user_data
def get_polar_user_info(self, physical=False): # pragma: no cover
r = self.rower
_ = self.open()
authorizationstring = str('Bearer ' + r.polartoken)
headers = {
'Authorization': authorizationstring,
'Accept': 'application/json'
}
if not physical:
url = baseurl+'/users/{userid}'.format(
userid=r.polaruserid
)
else:
url = 'https://www.polaraccesslink.com/v3/users/{userid}/physical-information-transactions/'.format(
userid=r.polaruserid
)
if physical:
response = requests.post(url, headers=headers)
else:
response = requests.get(url, headers=headers)
return response
def get_workout(self, id, transaction_id) -> int:
r = self.rower
_ = self.open()
authorizationstring = str('Bearer ' + r.polartoken)
headers = {
'Authorization': authorizationstring,
'Accept': 'application/json'
}
url = baseurl+'/users/{userid}/exercise-transactions'.format(
userid=r.polaruserid
)
response = requests.post(url, headers=headers)
if response.status_code == 201:
transactionid = response.json()['transaction-id']
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
response = requests.get(url, headers=headers)
if response.status_code == 200:
exerciseurls = response.json()['exercises']
for exerciseurl in exerciseurls:
response = requests.get(exerciseurl, headers=headers)
if response.status_code == 200:
exercise_dict = response.json()
thisid = exercise_dict['id']
if thisid == id:
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}' \
'/exercises/{exerciseid}/tcx'.format(
userid=r.polaruserid,
transactionid=transactionid,
exerciseid=id)
authorizationstring = str('Bearer ' + r.polartoken)
headers2 = {
'Authorization': authorizationstring,
}
response = requests.get(url, headers=headers2)
if response.status_code == 200:
result = response.content
# commit transaction
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
response = requests.put(url, headers=headers)
dologging(
'polar.log', 'Committing transaction on {url}'.format(url=url))
else: # pragma: no cover
result = None
return result
def get_workout_list(self, *args, **kwargs) -> list:
exercises = self.get_polar_workouts(self.user)
workouts = []
try:
a = exercises.status_code
return []
except:
return []
for exercise in exercises:
try:
d = exercise['distance']
except KeyError:
d = 0
i = exercise['id']
transactionid = exercise['transaction-id']
starttime = exercise['start-time']
rowtype = exercise['sport']
durationstring = exercise['duration']
duration = isodate.parse_duration(durationstring)
keys = ['id', 'distance', 'duration',
'starttime', 'rowtype', 'source', 'name', 'new']
values = [i, d, duration, starttime, rowtype, transactionid, '', '']
res = dict(zip(keys, values))
workouts.append(res)
return workouts
def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover
state = str(uuid4())
params = {"client_id": POLAR_CLIENT_ID,
"response_type": "code",
# "redirect_uri": POLAR_REDIRECT_URI,
"state": state,
# "scope":"accesslink.read_all"
}
url = "https://flow.polar.com/oauth2/authorization?" + \
urllib.parse.urlencode(params)
dologging('polar.log', 'Authorizing')
dologging('polar.log', url)
dologging('polar.log', params)
return url
def get_token(self, code, *args, **kwargs) -> (str, int, str):
post_data = {"grant_type": "authorization_code",
"code": code,
# "redirect_uri": POLAR_REDIRECT_URI,
}
auth_string = '{id}:{secret}'.format(
id=POLAR_CLIENT_ID,
secret=POLAR_CLIENT_SECRET
)
try:
headers = {'Authorization': 'Basic %s' % base64.b64encode(auth_string)}
except TypeError:
headers = {'Authorization': 'Basic %s' % base64.b64encode(
bytes(auth_string, 'utf-8')).decode('utf-8')}
dologging('polar.log', 'Getting token')
dologging('polar.log', post_data)
dologging('polar.log', auth_string)
response = requests.post("https://polarremote.com/v2/oauth2/token",
data=post_data,
headers=headers)
if response.status_code != 200: # pragma: no cover
dologging('polar.log', 'Getting token, got:')
dologging('polar.log', response.status_code)
dologging('polar.log', response.reason)
dologging('polar.log', response.text)
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
user_id = token_json['x_user_id']
dologging('polar.log', response.status_code)
try:
dologging('polar.log', response.text)
except AttributeError:
pass
dologging('polar.log', token_json)
except (KeyError, JSONDecodeError) as e: # pragma: no cover
dologging('polar.log', e)
try:
dologging('polar.log', response.text)
except AttributeError:
pass
thetoken = 0
expires_in = 0
user_id = 0
return [thetoken, expires_in, user_id]
def open(self, *args, **kwargs) -> str:
r = self.rower
if (r.polartoken == '') or (r.polartoken is None):
s = "Token doesn't exist. Need to authorize"
raise NoTokenError(s)
elif (timezone.now() > r.polartokenexpirydate):
s = "Token expired. Needs to refresh"
raise NoTokenError(s)
token = self.rower.polartoken
return token
def token_refresh(self, *args, **kwargs) -> str:
raise NotImplementedError
# just as a quick test during development
u = User.objects.get(id=1)
nk_integration_1 = PolarIntegration(u)
+259
View File
@@ -0,0 +1,259 @@
from .integrations import SyncIntegration, NoTokenError
from rowers.models import User, Rower, Workout, TombStone
from rowers.tasks import handle_rp3_async_workout
from rowsandall_app.settings import (
RP3_CLIENT_ID, RP3_CLIENT_KEY, RP3_REDIRECT_URI, RP3_CLIENT_SECRET,
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET
)
from rowers.utils import myqueue, NoTokenError, dologging, uniqify
from django.utils import timezone
import requests
import pandas as pd
import arrow
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
from datetime import timedelta
graphql_url = "https://rp3rowing-app.com/graphql"
class RP3Integration(SyncIntegration):
def __init__(self, *args, **kwargs):
super(RP3Integration, self).__init__(*args, **kwargs)
self.oauth_data = {
'client_id': RP3_CLIENT_ID,
'client_secret': RP3_CLIENT_SECRET,
'redirect_uri': RP3_REDIRECT_URI,
'autorization_uri': "https://rp3rowing-app.com/oauth/authorize?",
'content_type': 'application/x-www-form-urlencoded',
# 'content_type': 'application/json',
'tokenname': 'rp3token',
'refreshtokenname': 'rp3refreshtoken',
'expirydatename': 'rp3tokenexpirydate',
'bearer_auth': False,
'base_url': "https://rp3rowing-app.com/oauth/token",
'scope': 'read,write',
}
def get_name(self):
return "RP3 Logbook"
def get_shortname(self):
return "rp3"
def createworkoutdata(self, w, *args, **kwargs):
return None
def workout_export(self, workout, *args, **kwargs) -> str:
pass
def get_workouts(self, *args, **kwargs) -> int:
auth_token = self.open()
r = self.rower
workouts_json = self.get_workout_list_json()
workouts_list = pd.json_normalize(workouts_json['data']['workouts'])
try:
rp3ids = workouts_list['id'].values
workouts_list.set_index('id',inplace=True)
except (KeyError, IndexError):
return 0
knownrp3ids = uniqify([
w.uploadedtorp3 for w in Workout.objects.filter(user=r)
])
dologging('rp3_import.log',rp3ids)
newids = [rp3id for rp3id in rp3ids if rp3id not in knownrp3ids]
dologging('rp3_import.log',newids)
for id in newids:
startdatetime = workouts_list.loc[id, 'executed_at_iso8601']
dologging('rp3_import.log', startdatetime)
_ = myqueue(
queuehigh,
handle_rp3_async_workout,
self.user.id,
auth_token,
id,
startdatetime,
20,
timezone = self.rower.defaulttimezone
)
return 1
def get_workout(self, id, *args, **kwargs) -> int:
startdatetime = kwargs.get('startdatetime', None)
if not startdatetime:
startdatetime = str(timezone.now())
auth_token = self.open()
_ = myqueue(
queuehigh,
handle_rp3_async_workout,
self.user.id,
auth_token,
id,
startdatetime,
20,
timezone = self.rower.defaulttimezone
)
def get_workout_schema(self, *args, **kwargs) -> dict:
auth_token = self.open()
headers = {'Authorization': 'Bearer ' + auth_token}
get_schema = """{
__type(name:"Workout") {
name
fields {
name
description
type {
name
kind
ofType {
name
kind
}
}
}
}
}"""
response = requests.post(
url = graphql_url,
headers=headers,
json={'query':get_schema}
)
return response.json()
def get_workout_list_json(self, *args, **kwargs) -> dict:
auth_token = self.open()
r = self.rower
headers = {'Authorization': 'Bearer ' + auth_token}
get_workouts_list = """{
workouts{
id
executed_at_iso8601
}
}"""
response = requests.post(
url=graphql_url,
headers=headers,
json={'query': get_workouts_list}
)
if (response.status_code != 200): # pragma: no cover
raise NoTokenError("Need to authorize")
return response.json()
def get_workout_list(self, *args, **kwargs) -> list:
r = self.rower
workouts_json = self.get_workout_list_json(*args, **kwargs)
workouts_list = pd.json_normalize(workouts_json['data']['workouts'])
knownrp3ids = uniqify([
w.uploadedtorp3 for w in Workout.objects.filter(user=r)
])
workouts = []
for key, data in workouts_list.iterrows():
try:
i = data['id']
except KeyError: # pragma: no cover
i = 0
if i in knownrp3ids: # pragma: no cover
nnn = ''
else:
nnn = 'NEW'
try:
s = arrow.get(data['executed_at_iso8601']).isoformat()
except KeyError: # pragma: no cover
s = ''
keys = ['id', 'distance', 'duration', 'starttime',
'rowtype', 'source', 'name', 'new']
values = [i, '', '', s, '', 'rp3', '', nnn]
res = dict(zip(keys, values))
workouts.append(res)
return workouts
def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover
params = {"client_id": RP3_CLIENT_KEY,
"response_type": "code",
"redirect_uri": RP3_REDIRECT_URI,
}
url = "https://rp3rowing-app.com/oauth/authorize/?" + \
urllib.parse.urlencode(params)
return url
def get_token(self, code, *args, **kwargs) -> (str, int, str):
post_data = {
"client_id": RP3_CLIENT_KEY,
"grant_type": "authorization_code",
"code": code,
"redirect_uri": RP3_REDIRECT_URI,
"client_secret": RP3_CLIENT_SECRET,
}
response = requests.post(
"https://rp3rowing-app.com/oauth/token",
data=post_data, verify=False,
)
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
except KeyError:
thetoken = ""
expires_in = 0
refresh_token = ""
return thetoken, expires_in, refresh_token
def open(self, *args, **kwargs) -> str:
tokenexpirydate = self.user.rower.rp3tokenexpirydate
if tokenexpirydate is None:
raise NoTokenError("No Token")
if tokenexpirydate is not None and timezone.now()-timedelta(days=120)>tokenexpirydate:
self.rower.rp3tokenexpirydate = timezone.now()-timedelta(days=1)
self.rower.save()
raise NoTokenError("No Token")
return super(RP3Integration, self).open()
def token_refresh(self, *args, **kwargs) -> str:
return super(RP3Integration, self).token_refresh(*args, **kwargs)
+325
View File
@@ -0,0 +1,325 @@
from .integrations import SyncIntegration, NoTokenError
from rowers.models import User, Rower, Workout, TombStone
from rowingdata import rowingdata
from rowers.tasks import handle_sporttracks_sync, handle_sporttracks_workout_from_data
from rowers.rower_rules import is_workout_user
import rowers.mytypes as mytypes
from rowsandall_app.settings import (
SPORTTRACKS_CLIENT_SECRET, SPORTTRACKS_CLIENT_ID,
SPORTTRACKS_REDIRECT_URI
)
import re
import numpy
import pytz
import json
import requests
import datetime
import pandas as pd
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
from rowers.utils import myqueue, dologging, uniqify
def getidfromuri(uri): # pragma: no cover
m = re.search('/(\w.*)\/(\d+)', uri)
return m.group(2)
def getidfromresponse(response): # pragma: no cover
t = response.json()
uri = t['uris'][0]
regex = '.*?sporttracks\.mobi\/api\/v2\/fitnessActivities/(\d+)\.json$'
m = re.compile(regex).match(uri).group(1)
id = int(m)
return int(id)
def default(o): # pragma: no cover
if isinstance(o, numpy.int64):
return int(o)
raise TypeError
class SportTracksIntegration(SyncIntegration):
def __init__(self, *args, **kwargs):
super(SportTracksIntegration, self).__init__(*args, **kwargs)
self.oauth_data = {
'client_id': SPORTTRACKS_CLIENT_ID,
'client_secret': SPORTTRACKS_CLIENT_SECRET,
'redirect_uri': SPORTTRACKS_REDIRECT_URI,
'authorization_uri': "https://api.sporttracks.mobi/oauth2/authorize",
'content_type': 'application/json',
'tokenname': 'sporttrackstoken',
'refreshtokenname': 'sporttracksrefreshtoken',
'expirydatename': 'sporttrackstokenexpirydate',
'bearer_auth': False,
'base_url': "https://api.sporttracks.mobi/oauth2/token",
'scope': 'write',
}
def get_name(self):
return "SportTracks"
def get_shortname(self):
return "sporttracks"
def open(self, *args, **kwargs) -> str:
return super(SportTracksIntegration, self).open(*args, **kwargs)
def createworkoutdata(self, w, *args, **kwargs):
timezone = pytz.timezone(w.timezone)
filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except: # pragma: no cover
return {}
try:
averagehr = int(row.df[' HRCur (bpm)'].mean())
maxhr = int(row.df[' HRCur (bpm)'].max())
except KeyError: # pragma: no cover
averagehr = 0
maxhr = 0
try:
duration = w.duration.hour*3600
duration += w.duration.minute*60
duration += w.duration.second
duration += +1.0e-6*w.duration.microsecond
except AttributeError: # pragma: no cover
return {}
t = row.df.loc[:, 'TimeStamp (sec)'].values - \
row.df.loc[:, 'TimeStamp (sec)'].iloc[0]
try:
t[0] = t[1]
except IndexError: # pragma: no cover
return {}
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).values
spm[0] = spm[1]
hr = row.df[' HRCur (bpm)'].astype(int).values
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:
haslatlon = 0
haspower = 1
try:
power = row.df[' Power (watts)'].astype(int).values
except KeyError: # pragma: no cover
haspower = 0
locdata = []
hrdata = []
spmdata = []
distancedata = []
powerdata = []
t = t.tolist()
hr = hr.tolist()
d = d.tolist()
spm = spm.tolist()
if haslatlon:
lat = lat.tolist()
lon = lon.tolist()
power = power.tolist()
for i in range(len(t)):
hrdata.append(t[i])
hrdata.append(hr[i])
distancedata.append(t[i])
distancedata.append(d[i])
spmdata.append(t[i])
spmdata.append(spm[i])
if haslatlon:
locdata.append(t[i])
locdata.append([lat[i], lon[i]])
if haspower:
powerdata.append(t[i])
powerdata.append(power[i])
try:
w.notes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
w.notes = 'from '+w.workoutsource+' via rowsandall.com'
st = w.startdatetime.astimezone(timezone)
st = st.replace(microsecond=0)
data = {
"type": "Rowing",
"name": w.name,
"start_time": st.isoformat(),
"total_distance": int(w.distance),
"duration": duration,
"notes": w.notes,
"avg_heartrate": averagehr,
"max_heartrate": maxhr,
"distance": distancedata,
"cadence": spmdata,
"heartrate": hrdata,
}
if haslatlon:
data = {
"type": "Rowing",
"name": w.name,
"start_time": st.isoformat(),
"total_distance": int(w.distance),
"duration": duration,
"notes": w.notes,
"avg_heartrate": averagehr,
"max_heartrate": maxhr,
"location": locdata,
"distance": distancedata,
"cadence": spmdata,
"heartrate": hrdata,
}
if haspower:
data['power'] = powerdata
return data
def workout_export(self, workout, *args, **kwargs) -> str:
thetoken = self.open()
stid = "0"
# ready to upload. Hurray
if not(is_workout_user(self.user, workout)):
return "0"
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
data = self.createworkoutdata(workout)
if not data:
return "0"
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json"
_ = myqueue(
queue,
handle_sporttracks_sync,
workout.id,
url,
headers,
json.dumps(data, default=default))
return 1
def get_workouts(self, *args, **kwargs) -> int:
r = self.rower
workouts_json = self.get_workout_list_json(*args, **kwargs)
stids = [int(getidfromuri(item['uri']))
for item in workouts_json['items']]
knownstids = uniqify([
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
])
newids = [stid for stid in stids if stid not in knownstids]
for sporttracksid in newids:
id = self.get_workout(sporttracksid)
return 1
def get_workout(self, id) -> int:
_ = self.open()
r = self.rower
job = myqueue(
queue,
handle_sporttracks_workout_from_data,
self.user,
id,
'sporttracks',
'sporttracks'
)
return job.id
def get_workout_list_json(self, *args, **kwargs) -> dict:
_ = self.open()
r = self.rower
authorizationstring = str('Bearer ' + r.sporttrackstoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities"
res = requests.get(url, headers=headers)
if (res.status_code != 200):
s = "Token doesn't exist. Need to authorize"
raise NoTokenError(s)
return res.json()
def get_workout_list(self, *args, **kwargs) -> list:
r = self.rower
workouts_json = self.get_workout_list_json(*args, **kwargs)
workouts = []
knownstids = uniqify([
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
])
for item in workouts_json['items']:
d = int(float(item['total_distance']))
i = int(getidfromuri(item['uri']))
if i in knownstids: # pragma: no cover
nnn = ''
else:
nnn = 'NEW'
n = item['name']
ttot = str(datetime.timedelta(seconds=int(float(item['duration']))))
s = item['start_time']
r = item['type']
keys = ['id', 'distance', 'duration',
'starttime', 'rowtype', 'source', 'name', 'new']
values = [i, d, ttot, s, r, None, n, nnn]
res = dict(zip(keys, values))
workouts.append(res)
return workouts
def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover
return super(SportTracksIntegration, self).make_authorization_url(*args, **kwargs)
def get_token(self, code, *args, **kwargs) -> (str, int, str):
return super(SportTracksIntegration, self).get_token(code, *args, **kwargs)
def token_refresh(self, *args, **kwargs) -> str:
return super(SportTracksIntegration, self).token_refresh(*args, **kwargs)
+346
View File
@@ -0,0 +1,346 @@
from .integrations import SyncIntegration, NoTokenError
from rowers.models import User, Rower, Workout, TombStone
from rowingdata import rowingdata
from rowers import mytypes
from rowers.tasks import handle_strava_sync, fetch_strava_workout
from stravalib.exc import ActivityUploadFailed, TimeoutExceeded
from rowers.rower_rules import is_workout_user, ispromember
from rowers.utils import get_strava_stream
from rowers.utils import myqueue, dologging
from rowers.imports import *
import gzip
import time
import requests
import arrow
import datetime
from rowsandall_app.settings import (
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
SITE_URL
)
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
webhookverification = "kudos_to_rowing"
webhooklink = SITE_URL+'/rowers/strava/webhooks/'
headers = {'Accept': 'application/json',
'Api-Key': STRAVA_CLIENT_ID,
'Content-Type': 'application/json',
'user-agent': 'sanderroosendaal'}
from json.decoder import JSONDecodeError
from rowers.dataprep import columndict
def strava_establish_push(): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions"
post_data = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
'callback_url': webhooklink,
'verify_token': webhookverification,
}
response = requests.post(url, data=post_data)
return response.status_code
def strava_list_push(): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions"
params = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
}
response = requests.get(url, params=params)
if response.status_code == 200:
data = response.json()
return [w['id'] for w in data]
return []
def strava_push_delete(id): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions/{id}".format(id=id)
params = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
}
response = requests.delete(url, json=params)
return response.status_code
class StravaIntegration(SyncIntegration):
def __init__(self, *args, **kwargs):
super(StravaIntegration, self).__init__(*args, **kwargs)
self.oauth_data = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
'redirect_uri': STRAVA_REDIRECT_URI,
'autorization_uri': "https://www.strava.com/oauth/authorize",
'content_type': 'application/json',
'tokenname': 'stravatoken',
'refreshtokenname': 'stravarefreshtoken',
'expirydatename': 'stravatokenexpirydate',
'bearer_auth': True,
'base_url': "https://www.strava.com/oauth/token",
'grant_type': 'refresh_token',
'headers': headers,
'scope': 'activity:write,activity:read_all',
}
def get_token(self, code, *args, **kwargs):
return super(StravaIntegration, self).get_token(code, *args, **kwargs)
def get_name(self):
return "Strava"
def get_shortname(self):
return "strava"
def open(self, *args, **kwargs):
dologging('strava_log.log','Getting token for user {id}'.format(id=self.rower.id))
token = super(StravaIntegration, self).open(*args, **kwargs)
if self.rower.strava_owner_id == 0:
_ = self.set_strava_athlete_id()
return token
# createworkoutdata
def createworkoutdata(self, w, *args, **kwargs) -> str:
dozip = kwargs.get('dozip', True)
filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except IOError: # pragma: no cover
data = dataprep.read_df_sql(w.id)
try:
datalength = len(data)
except AttributeError:
datalength = 0
if datalength != 0:
data.rename(columns=columndict, inplace=True)
_ = data.to_csv(w.csvfilename+'.gz',
index_label='index',
compression='gzip')
try:
row = rowingdata(csvfile=filename)
except IOError:
return ''
else:
return ''
tcxfilename = filename[:-4]+'.tcx'
try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
row.exporttotcx(tcxfilename, notes=newnotes)
if dozip:
gzfilename = tcxfilename+'.gz'
with open(tcxfilename, 'rb') as inF:
s = inF.read()
with gzip.GzipFile(gzfilename, 'wb') as outF:
outF.write(s)
try:
os.remove(tcxfilename)
except WindowError: # pragma: no cover
pass
return gzfilename
return tcxfilename
# workout_export
def workout_export(self, workout, *args, **kwargs) -> str:
description = kwargs.get('description','')
quick = kwargs.get('quick',False)
try:
_ = self.open()
except NoTokenError:
return 0
if (self.rower.stravatoken == '') or (self.rower.stravatoken is None):
raise NoTokenError("Your hovercraft is full of eels")
if not (is_workout_user(self.user, workout)):
return 0
tcxfile = self.createworkoutdata(workout)
if not tcxfile:
return 0
activity_type = self.rower.stravaexportas
if activity_type == 'match':
try:
activity_type = mytypes.stravamapping[workout.workouttype]
except KeyError:
activity_type = 'Rowing'
_ = myqueue(queue,
handle_strava_sync,
self.rower.stravatoken,
workout.id,
tcxfile, workout.name, activity_type,
workout.notes)
dologging('strava_export_log.log', 'Exporting as {t} from {w}'.format(
t=activity_type, w=workout.workouttype))
return 1
# get_workouts
def get_workouts(workout, *args, **kwargs) -> int:
return NotImplemented
# get_workout
def get_workout(self, id) -> int:
try:
_ = self.open()
except NoTokenError:
return 0
csvfilename = 'media/{code}_{id}.csv'.format(
code=uuid4().hex[:16], id=id)
job = myqueue(queue,
fetch_strava_workout,
self.rower.stravatoken,
self.oauth_data,
id,
csvfilename,
self.user.id,
)
return job.id
# get_workout_list
def get_workout_list(self, *args, **kwargs) -> list:
limit_n = kwargs.get('limit_n',0)
if (self.rower.stravatoken == '') or (self.rower.stravatoken is None): # pragma: no cover
raise NoTokenError
elif (self.rower.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > self.rower.stravatokenexpirydate): # pragma: no cover
raise NoTokenError
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + self.rower.stravatoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://www.strava.com/api/v3/athlete/activities"
if limit_n == 0:
params = {}
else: # pragma: no cover
params = {'per_page': limit_n}
res = requests.get(url, headers=headers, params=params)
if (res.status_code != 200): # pragma: no cover
return []
workouts = []
rower = self.rower
stravaids = [int(item['id']) for item in res.json()]
stravadata = [{
'id': int(item['id']),
'elapsed_time':item['elapsed_time'],
'start_date':item['start_date'],
} for item in res.json()]
wfailed = Workout.objects.filter(user=rower, uploadedtostrava=-1)
for w in wfailed: # pragma: no cover
for item in stravadata:
elapsed_time = item['elapsed_time']
start_date = item['start_date']
stravaid = item['id']
if arrow.get(start_date) == arrow.get(w.startdatetime):
elapsed_td = datetime.timedelta(seconds=int(elapsed_time))
elapsed_time = datetime.datetime.strptime(
str(elapsed_td),
"%H:%M:%S"
)
if str(elapsed_time)[-7:] == str(w.duration)[-7:]:
w.uploadedtostrava = int(stravaid)
w.save()
knownstravaids = uniqify([
w.uploadedtostrava for w in Workout.objects.filter(user=self.rower)
])
for item in res.json():
d = int(float(item['distance']))
i = item['id']
if i in knownstravaids: # pragma: no cover
nnn = ''
else:
nnn = 'NEW'
n = item['name']
ttot = str(datetime.timedelta(
seconds=int(float(item['elapsed_time']))))
s = item['start_date']
r = item['type']
s2 = None
keys = ['id', 'distance', 'duration',
'starttime', 'rowtype', 'source', 'name', 'new']
values = [i, d, ttot, s, r, s2, n, nnn]
res2 = dict(zip(keys, values))
workouts.append(res2)
return workouts
# make_authorization_url
def make_authorization_url(self, *args, **kwargs):
params = {"client_id": STRAVA_CLIENT_ID,
"response_type": "code",
"redirect_uri": STRAVA_REDIRECT_URI,
"scope": "activity:write,activity:read_all"}
url = "https://www.strava.com/oauth/authorize?" + \
urllib.parse.urlencode(params)
return url
# token_refresh
def token_refresh(self, *args, **kwargs):
return super(StravaIntegration, self).token_refresh(*args, **kwargs)
def set_strava_athlete_id(self, *args, **kwargs):
r = self.rower
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 None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate):
_ = self.open()
authorizationstring = str('Bearer ' + r.stravatoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://www.strava.com/api/v3/athlete"
response = requests.get(url, headers=headers, params={})
if response.status_code == 200: # pragma: no cover
r.strava_owner_id = response.json()['id']
r.save()
return response.json()['id']
return 0
+149
View File
@@ -0,0 +1,149 @@
from .integrations import SyncIntegration, NoTokenError
from rowers.models import User, Rower, Workout, TombStone
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
from rowers.utils import myqueue, dologging, myqueue
import requests
from rowingdata import rowingdata
from rowers.rower_rules import is_workout_user
import time
from django_rq import job
from rowers.tasks import check_tp_workout_id, handle_workout_tp_upload
from rowsandall_app.settings import (
TP_CLIENT_ID, TP_CLIENT_SECRET,
TP_REDIRECT_URI, TP_CLIENT_KEY,TP_API_LOCATION,
TP_OAUTH_LOCATION,
)
import gzip
import base64
from io import BytesIO
tpapilocation = TP_API_LOCATION
class TPIntegration(SyncIntegration):
def __init__(self, *args, **kwargs):
super(TPIntegration, self).__init__(*args, **kwargs)
self.oauth_data = {
'client_id': TP_CLIENT_ID,
'client_secret': TP_CLIENT_SECRET,
'redirect_uri': TP_REDIRECT_URI,
'autorization_uri': "https://oauth.trainingpeaks.com/oauth/authorize?",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'tptoken',
'refreshtokenname': 'tprefreshtoken',
'expirydatename': 'tptokenexpirydate',
'bearer_auth': False,
'base_url': "https://oauth.trainingpeaks.com/oauth/token",
'scope': 'write',
}
def get_name(self):
return "TrainingPeaks"
def get_shortname(self):
return "trainingpeaks"
def createworkoutdata(self, w, *args, **kwargs):
filename = w.csvfilename
row = rowingdata(csvfile=filename)
tcxfilename = filename[:-4]+'.tcx'
try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
row.exporttotcx(tcxfilename, notes=newnotes)
return tcxfilename
def workout_export(self, workout, *args, **kwargs) -> str:
thetoken = self.open()
tcxfilename = self.createworkoutdata(workout)
job = myqueue(
queue,
handle_workout_tp_upload,
workout,
thetoken,
tcxfilename
)
return job.id
def get_workouts(self, *args, **kwargs) -> int:
raise NotImplementedError("not implemented")
def get_workout(self, id) -> int:
raise NotImplementedError("not implemented")
def get_workout_list(self, *args, **kwargs) -> list:
raise NotImplementedError("not implemented")
def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover
params = {"client_id": TP_CLIENT_KEY,
"response_type": "code",
"redirect_uri": TP_REDIRECT_URI,
"scope": "file:write",
}
url = TP_OAUTH_LOCATION+"oauth/authorize/?" + \
urllib.parse.urlencode(params)
return url
def get_token(self, code, *args, **kwargs) -> (str, int, str):
# client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET)
post_data = {
"client_id": TP_CLIENT_KEY,
"grant_type": "authorization_code",
"code": code,
"redirect_uri": TP_REDIRECT_URI,
"client_secret": TP_CLIENT_SECRET,
}
response = requests.post(
TP_OAUTH_LOCATION+"/oauth/token/",
data=post_data, verify=False,
)
if response.status_code != 200:
raise NoTokenError
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
except KeyError: # pragma: no cover
thetoken = ""
expires_in = 0
refresh_token = ""
return thetoken, expires_in, refresh_token
def open(self, *args, **kwargs) -> str:
return super(TPIntegration, self).open(*args, **kwargs)
def token_refresh(self, *args, **kwargs) -> str:
return super(TPIntegration, self).token_refresh(*args, **kwargs)
# just as a quick test during development
u = User.objects.get(id=1)
integration_1 = TPIntegration(u)
+4 -3
View File
@@ -17,7 +17,8 @@ import rowers.c2stuff as c2stuff
import rowers.metrics as metrics
import rowers.dataprep as dataprep
from rowers.dataprep import rdata
import rowers.stravastuff as stravastuff
import rowers.utils as utils
from scipy.interpolate import griddata
from scipy.signal import savgol_filter
from scipy import optimize
@@ -5401,7 +5402,7 @@ def interactive_flexchart_stacked(id, r, xparam='time',
if metricsdicts[column]['maysmooth']:
nrsteps = int(log2(r.usersmooth))
for i in range(nrsteps):
rowdata[column] = stravastuff.ewmovingaverage(
rowdata[column] = utils.ewmovingaverage(
rowdata[column], 5)
except KeyError:
pass
@@ -5767,7 +5768,7 @@ def interactive_flex_chart2(id, r, promember=0,
if metricsdicts[column]['maysmooth']:
nrsteps = int(log2(r.usersmooth))
for i in range(nrsteps):
rowdata[column] = stravastuff.ewmovingaverage(
rowdata[column] = utils.ewmovingaverage(
rowdata[column], 5)
except KeyError:
pass
+11 -11
View File
@@ -27,13 +27,8 @@ from rowingdata import rowingdata as rrdata
import rowers.uploads as uploads
import rowers.polarstuff as polarstuff
import rowers.c2stuff as c2stuff
import rowers.rp3stuff as rp3stuff
import rowers.stravastuff as stravastuff
import rowers.nkstuff as nkstuff
from rowers.opaque import encoder
from rowers.integrations import *
from rowers.rower_rules import user_is_not_basic, user_is_coachee
from rowers.utils import dologging
@@ -79,8 +74,9 @@ class Command(BaseCommand):
def handle(self, *args, **options):
# Polar
try:
polar_available = polarstuff.get_polar_notifications()
_ = polarstuff.get_all_new_workouts(polar_available)
polarintegration = PolarIntegration(None)
_ = polarintegration.get_workouts()
except: # pragma: no cover
exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
@@ -91,7 +87,9 @@ class Command(BaseCommand):
rowers = Rower.objects.filter(c2_auto_import=True)
for r in rowers: # pragma: no cover
if user_is_not_basic(r.user) or user_is_coachee(r.user):
c2stuff.get_c2_workouts(r)
c2integration = C2Integration(r.user)
_ = c2integration.get_workouts()
except: # pragma: no cover
exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
@@ -101,7 +99,8 @@ class Command(BaseCommand):
rowers = Rower.objects.filter(rp3_auto_import=True)
for r in rowers: # pragma: no cover
if user_is_not_basic(r.user) or user_is_coachee(r.user):
_ = rp3stuff.get_rp3_workouts(r)
rp3_integration = RP3Integration(r.user)
_ = rp3_integration.get_workouts()
except: # pragma: no cover
exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
@@ -112,7 +111,8 @@ class Command(BaseCommand):
for r in rowers: # pragma: no cover
dologging("nklog.log","NK Auto import set for rower {id}".format(id=r.user.id))
if user_is_not_basic(r.user) or user_is_coachee(r.user):
_ = nkstuff.get_nk_workouts(r)
nk_integration = NKIntegration(r.user)
_ = nk_integration.get_workouts()
except: # pragma: no cover
exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
-284
View File
@@ -1,284 +0,0 @@
from requests.auth import HTTPBasicAuth
from rowers.nkimportutils import *
from rowers.imports import *
from rowers.tasks import handle_nk_async_workout
from rowsandall_app.settings import (
NK_CLIENT_ID, NK_REDIRECT_URI, NK_CLIENT_SECRET,
SITE_URL, NK_API_LOCATION, NK_OAUTH_LOCATION,
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET,
)
import gzip
import rowers.mytypes as mytypes
from rowers.utils import myqueue
from iso8601 import ParseError
from rowers.rower_rules import is_workout_user, ispromember
import time
from time import strftime
import requests
from rowers.utils import dologging
from json.decoder import JSONDecodeError
# https:#oauth-stage.nkrowlink.com/oauth/authorizegrant_type=authorization_code&response_type=code&client_id=rowsandall-staging&scope=read&state=fc8fc3d8-ce0a-443e-838a-1c06fb5317c6&redirect_uri=https%3A%2F%2Fdunav.ngrok.io%2Fnk_callback%2F
# https:#oauth-stage.nkrowlink.com/oauth/authorize?grant_type=authorization_code&response_type=code&client_id=rowsandall-staging&scope=read&state=1234&redirect_uri=https%3A%2F%2Fdev.rowsandall.com%2Fnk_callback
from requests_oauthlib import OAuth2Session
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
oauth_data = {
'client_id': NK_CLIENT_ID,
'client_secret': NK_CLIENT_SECRET,
'redirect_uri': NK_REDIRECT_URI,
'autorization_uri': NK_OAUTH_LOCATION+"/oauth/authorize",
'content_type': 'application/json',
'tokenname': 'nktoken',
'refreshtokenname': 'nkrefreshtoken',
'expirydatename': 'nktokenexpirydate',
'bearer_auth': True,
'base_url': NK_OAUTH_LOCATION+"/oauth/token",
'scope': 'read',
}
def get_token(code): # pragma: no cover
url = oauth_data['base_url']
post_data = {"client_id": oauth_data['client_id'],
"grant_type": "authorization_code",
"redirect_uri": oauth_data['redirect_uri'],
"code": code,
}
response = requests.post(url, auth=HTTPBasicAuth(oauth_data['client_id'], oauth_data['client_secret']),
data=post_data)
if response.status_code != 200:
return [0, response.text, 0, 0]
token_json = response.json()
access_token = token_json['access_token']
refresh_token = token_json['refresh_token']
expires_in = token_json['expires_in']
nk_owner_id = token_json['user_id']
return [access_token, expires_in, refresh_token, nk_owner_id]
def nk_open(user):
r = Rower.objects.get(user=user)
if (r.nktoken == '') or (r.nktoken is None): # pragma: no cover
raise NoTokenError("User has no token")
else:
if (timezone.now() > r.nktokenexpirydate):
thetoken = rower_nk_token_refresh(user)
if thetoken is None: # pragma: no cover
raise NoTokenError("User has no token")
return thetoken
else:
thetoken = r.nktoken
return thetoken
def get_nk_workouts(rower, do_async=True, before=0, after=0):
try:
_ = nk_open(rower.user)
except NoTokenError: # pragma: no cover
dologging("nklog.log","NK Token error for user {id}".format(id=rower.user.id))
return 0
res = get_nk_workout_list(rower.user, before=before, after=after)
if res.status_code != 200: # pragma: no cover
dologging('nklog.log','Status code {code}'.format(code=res.status_code))
return 0
#dologging('nklog.log',json.dumps(res.json()))
nkids = [item['id'] for item in res.json()]
dologging('nklog.log',json.dumps(nkids))
alldata = {}
for item in res.json():
alldata[item['id']] = item
knownnkids = [
w.uploadedtonk for w in Workout.objects.filter(user=rower)
]
tombstones = [
t.uploadedtonk for t in TombStone.objects.filter(user=rower)
]
parkedids = []
try:
with open('nkblocked.json', 'r') as nkblocked:
jsondata = json.load(nkblocked)
parkedids = jsondata['ids']
except (FileNotFoundError, JSONDecodeError): # pragma: no cover
pass
knownnkids = uniqify(knownnkids+tombstones+parkedids)
newids = [nkid for nkid in nkids if nkid not in knownnkids]
newids2 = newids.copy()
dologging('nklog.log',json.dumps(newids))
dologging('nklog.log','Nr of new IDs is {i}'.format(i=len(newids)))
#if len(newids)>0:
# s = 'Starting NK Auto Import for user {id}'.format(id=r.user.id)
# dologging('nklog.log', s)
# s = 'New NK IDs {newids} (user {id})'.format(newids=newids,id=rower.user.id)
# dologging('nklog.log', s)
#else:
# dologging('nklog.log','Newids is false')
newparkedids = uniqify(newids+parkedids)
with open('nkblocked.json', 'wt') as nkblocked:
data = {'ids': newparkedids}
json.dump(data, nkblocked)
counter = 0
#dologging('nklog.log','Ik ben hier')
for nkid in newids2:
dologging('nklog.log','Queueing {id}'.format(id=nkid))
res = myqueue(queuehigh,
handle_nk_async_workout,
alldata,
rower.user.id,
rower.nktoken,
nkid,
counter,
rower.defaulttimezone
)
counter += 1
return 1
def do_refresh_token(refreshtoken):
post_data = {"grant_type": "refresh_token",
# "client_id":NK_CLIENT_ID,
"refresh_token": refreshtoken,
}
url = oauth_data['base_url']
response = requests.post(url, data=post_data, auth=HTTPBasicAuth(
oauth_data['client_id'], oauth_data['client_secret']))
if response.status_code != 200: # pragma: no cover
return [0, 0, 0]
token_json = response.json()
access_token = token_json['access_token']
refresh_token = token_json['refresh_token']
expires_in = token_json['expires_in']
return access_token, expires_in, refresh_token
def rower_nk_token_refresh(user):
r = Rower.objects.get(user=user)
res = do_refresh_token(r.nkrefreshtoken)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
r.nktoken = access_token
r.nktokenexpirydate = expirydatetime
r.nkrefreshtoken = refresh_token
r.save()
return r.nktoken
def make_authorization_url(request): # pragma: no cover
return imports_make_authorization_url(oauth_data)
def get_nk_workout_list(user, fake=False, after=0, before=0):
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"
return custom_exception_handler(401, s)
elif (r.nktokenexpirydate is None or
timezone.now()+timedelta(seconds=10) > r.nktokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh."
return custom_exception_handler(401, s)
else:
# ready to fetch. Hurray
if not before: # pragma: no cover
before = arrow.now()+timedelta(days=1)
before = str(int(before.timestamp())*1000)
if not after: # pragma: no cover
after = arrow.now()-timedelta(days=7)
after = str(int(after.timestamp())*1000)
authorizationstring = str('Bearer ' + r.nktoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json',
}
url = NK_API_LOCATION+"api/v1/sessions"
params = {
'after': after,
'before': before,
} # start / end time
s = requests.get(url, headers=headers, params=params)
return s
#
def get_workout(user, nkid, do_async=True, startdate='', enddate=''):
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"
return custom_exception_handler(401, s), 0
elif (timezone.now() > r.nktokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh."
return custom_exception_handler(401, s), 0
before = 0
after = 0
if startdate: # pragma: no cover
startdate = arrow.get(startdate)
after = str(int(startdate.timestamp())*1000)
if enddate: # pragma: no cover
enddate = arrow.get(enddate)
before = str(int(enddate.timestamp())*1000)
res = get_nk_workout_list(r.user, before=before, after=after)
if res.status_code != 200: # pragma: no cover
# dologging('nklog.log','Status code {code}'.format(code=res.status_code))
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,
)
return res
-1
View File
@@ -15,7 +15,6 @@ import math
from math import sin, cos, atan2, sqrt
import urllib
import rowers.c2stuff as c2stuff
# Django
from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
-495
View File
@@ -1,495 +0,0 @@
from rowers.rower_rules import ispromember
from stravalib.exc import ActivityUploadFailed, TimeoutExceeded
from rowers.models import Rower, Workout
import rowers.mytypes as mytypes
from rowers.utils import NoTokenError, custom_exception_handler
from rowers.utils import dologging
from rowsandall_app.settings import (
POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET, UPLOAD_SERVICE_URL
)
import stravalib
from io import StringIO
from rowers.dataprep import columndict
import rowers.dataprep as dataprep
from rowers.tasks import handle_request_post
import pandas as pd
from rowingdata import rowingdata
# All the functionality needed to connect to Strava
# Python
import oauth2 as oauth
import cgi
import requests
import requests.auth
import json
from django.utils import timezone
from datetime import datetime
import numpy as np
from dateutil import parser
import time
import math
from math import sin, cos, atan2, sqrt
import os
import sys
import gzip
import base64
import yaml
from uuid import uuid4
from requests import ConnectionError
from json.decoder import JSONDecodeError
# Django
from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.urls import reverse, reverse_lazy
from rowers.utils import myqueue
from rowers.opaque import encoder
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
# Project
# from .models import Profile
baseurl = 'https://polaraccesslink.com/v3'
# Exchange access code for long-lived access token
def get_token(code):
post_data = {"grant_type": "authorization_code",
"code": code,
# "redirect_uri": POLAR_REDIRECT_URI,
}
auth_string = '{id}:{secret}'.format(
id=POLAR_CLIENT_ID,
secret=POLAR_CLIENT_SECRET
)
try:
headers = {'Authorization': 'Basic %s' % base64.b64encode(auth_string)}
except TypeError:
headers = {'Authorization': 'Basic %s' % base64.b64encode(
bytes(auth_string, 'utf-8')).decode('utf-8')}
dologging('polar.log', 'Getting token')
dologging('polar.log', post_data)
dologging('polar.log', auth_string)
response = requests.post("https://polarremote.com/v2/oauth2/token",
data=post_data,
headers=headers)
if response.status_code != 200: # pragma: no cover
dologging('polar.log', 'Getting token, got:')
dologging('polar.log', response.status_code)
dologging('polar.log', response.reason)
dologging('polar.log', response.text)
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
user_id = token_json['x_user_id']
dologging('polar.log', response.status_code)
try:
dologging('polar.log', response.text)
except AttributeError:
pass
dologging('polar.log', token_json)
except (KeyError, JSONDecodeError) as e: # pragma: no cover
dologging('polar.log', e)
try:
dologging('polar.log', response.text)
except AttributeError:
pass
thetoken = 0
expires_in = 0
user_id = 0
return [thetoken, expires_in, user_id]
# Make authorization URL including random string
def make_authorization_url(): # 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": POLAR_CLIENT_ID,
"response_type": "code",
"redirect_uri": POLAR_REDIRECT_URI,
"scope": "write"}
import urllib
url = "https://flow.polar.com/oauth2/authorization" + \
urllib.parse.urlencode(params)
return HttpResponseRedirect(url)
def revoke_access(user): # pragma: no cover
headers = {
'Authorization': 'Bearer {token}'.format(token=user.rower.polartoken)
}
response = requests.delete('https://www.polaraccesslink.com/v3/users/{userid}'.format(
userid=user.rower.polaruserid
), headers=headers)
dologging('polar.log', response.text)
dologging('polar.log', response.reason)
return 1
def get_polar_notifications():
url = baseurl+'/notifications'
# state = str(uuid4())
auth_string = '{id}:{secret}'.format(
id=POLAR_CLIENT_ID,
secret=POLAR_CLIENT_SECRET
)
try:
headers = {'Authorization': 'Basic %s' % base64.b64encode(auth_string)}
except TypeError:
headers = {'Authorization': 'Basic %s' % base64.b64encode(
bytes(auth_string, 'utf-8')).decode('utf-8')}
try:
response = requests.get(url, headers=headers)
except ConnectionError: # pragma: no cover
response = {
'status_code': 400,
}
available_data = []
try:
if response.status_code == 200:
available_data = response.json()['available-user-data']
dologging('polar.log', available_data)
else: # pragma: no cover
dologging('polar.log', response.status_code)
dologging('polar.log', response.text)
except AttributeError: # pragma: no cover
try:
dologging('polar.log', response.text)
except AttributeError:
pass
pass
return available_data
def get_all_new_workouts(available_data, testing=False):
for record in available_data:
dologging('polar.log', str(record))
if testing: # pragma: no cover
print(record)
if record['data-type'] == 'EXERCISE':
try:
r = Rower.objects.get(polaruserid=record['user-id'])
u = r.user
if r.polar_auto_import and ispromember(u):
exercise_list = get_polar_workouts(u)
dologging('polar.log', exercise_list)
if testing: # pragma: no cover
print(exercise_list)
except Rower.DoesNotExist: # pragma: no cover
pass
return 1
def get_polar_workouts(user):
r = Rower.objects.get(user=user)
exercise_list = []
if (r.polartoken == '') or (r.polartoken is None):
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401, s)
elif (timezone.now() > r.polartokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh"
dologging('polar.log', s)
return custom_exception_handler(401, s)
else:
authorizationstring = str('Bearer ' + r.polartoken)
headers = {'Authorization': authorizationstring,
'Accept': 'application/json'}
headers2 = {
'Authorization': authorizationstring,
}
url = baseurl+'/users/{userid}/exercise-transactions'.format(
userid=r.polaruserid
)
response = requests.post(url, headers=headers)
dologging('polar.log', url)
dologging('polar.log', authorizationstring)
dologging('polar.log', str(response.status_code))
if response.status_code == 201:
transactionid = response.json()['transaction-id']
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
dologging('polar.log', url)
response = requests.get(url, headers=headers)
if response.status_code == 200:
exerciseurls = response.json()['exercises']
dologging('polar.log', exerciseurls)
for exerciseurl in exerciseurls:
response = requests.get(exerciseurl, headers=headers)
if response.status_code == 200:
exercise_dict = response.json()
tcxuri = exerciseurl+'/tcx'
response = requests.get(tcxuri, headers=headers2)
if response.status_code == 200:
filename = 'media/mailbox_attachments/{code}_{id}.tcx'.format(
id=exercise_dict['id'],
code=uuid4().hex[:16]
)
dologging('polar.log', filename)
with open(filename, 'wb') as fop:
fop.write(response.content)
workouttype = 'other'
try:
workouttype = mytypes.polaraccesslink_sports[
exercise_dict['detailed-sport-info']]
except KeyError: # pragma: no cover
dologging(
'polar.log', exercise_dict['detailed-sport-info'])
dologging('polar.log', workouttype)
try:
workouttype = mytypes.polarmappinginv[exercise_dict['sport'].lower(
)]
except KeyError:
dologging('polar.log', workouttype)
pass
dologging('polar.log', workouttype)
# post file to upload api
# TODO: add workouttype
uploadoptions = {
'title': '',
'workouttype': workouttype,
'boattype': '1x',
'user': user.id,
'secret': settings.UPLOAD_SERVICE_SECRET,
'file': filename,
'title': '',
}
url = settings.UPLOAD_SERVICE_URL
dologging('polar.log', uploadoptions)
dologging('polar.log', url)
_ = myqueue(
queuehigh,
handle_request_post,
url,
uploadoptions
)
dologging('polar.log', response.status_code)
if response.status_code != 200: # pragma: no cover
try:
dologging('polar.log', response.text)
except:
pass
try:
dologging('polar.log', response.json())
except:
pass
exercise_dict['filename'] = filename
else: # pragma: no cover
exercise_dict['filename'] = ''
exercise_list.append(exercise_dict)
dologging('polar.log', str(exercise_dict))
# commit transaction
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
requests.put(url, headers=headers)
dologging(
'polar.log', 'Committed transation at {url}'.format(url=url))
return exercise_list
def register_user(user, token):
r = Rower.objects.get(user=user)
if (r.polartoken == '') or (r.polartoken is None): # pragma: no cover
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401, s)
elif (timezone.now() > r.polartokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh"
return custom_exception_handler(401, s)
authorizationstring = 'Bearer {token}'.format(token=token)
headers = {
'Content-Type': 'application/xml',
'Authorization': authorizationstring,
'Accept': 'application/json'
}
payload = {
"member-id": encoder.encode_hex(user.id)
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer {token}'.format(token=token)
}
dologging('polar.log', 'Registering user')
response = requests.post(
'https://www.polaraccesslink.com/v3/users',
json=payload,
headers=headers
)
if response.status_code not in [200, 201]: # pragma: no cover
# dologging('polar.log',url)
dologging('polar.log', headers)
dologging('polar.log', payload)
dologging('polar.log', response.status_code)
dologging('polar.log', response.content)
try:
dologging('polar.log', response.reason)
dologging('polar.log', response.text)
except KeyError:
pass
return {}
polar_user_data = response.json()
return polar_user_data
def get_polar_user_info(user, physical=False): # pragma: no cover
r = Rower.objects.get(user=user)
if (r.polartoken == '') or (r.polartoken is None):
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401, s)
elif (timezone.now() > r.polartokenexpirydate):
s = "Token expired. Needs to refresh"
return custom_exception_handler(401, s)
authorizationstring = str('Bearer ' + r.polartoken)
headers = {
'Authorization': authorizationstring,
'Accept': 'application/json'
}
if not physical:
url = baseurl+'/users/{userid}'.format(
userid=r.polaruserid
)
else:
url = 'https://www.polaraccesslink.com/v3/users/{userid}/physical-information-transactions/'.format(
userid=r.polaruserid
)
if physical:
response = requests.post(url, headers=headers)
else:
response = requests.get(url, headers=headers)
return response
def get_polar_workout(user, id, transactionid):
r = Rower.objects.get(user=user)
if (r.polartoken == '') or (r.polartoken is None): # pragma: no cover
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401, s)
elif (timezone.now() > r.polartokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh"
return custom_exception_handler(401, s)
else:
authorizationstring = str('Bearer ' + r.polartoken)
headers = {
'Authorization': authorizationstring,
'Accept': 'application/json'
}
url = baseurl+'/users/{userid}/exercise-transactions'.format(
userid=r.polaruserid
)
response = requests.post(url, headers=headers)
if response.status_code == 201:
transactionid = response.json()['transaction-id']
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
response = requests.get(url, headers=headers)
if response.status_code == 200:
exerciseurls = response.json()['exercises']
for exerciseurl in exerciseurls:
response = requests.get(exerciseurl, headers=headers)
if response.status_code == 200:
exercise_dict = response.json()
thisid = exercise_dict['id']
if thisid == id:
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}' \
'/exercises/{exerciseid}/tcx'.format(
userid=r.polaruserid,
transactionid=transactionid,
exerciseid=id)
authorizationstring = str('Bearer ' + r.polartoken)
headers2 = {
'Authorization': authorizationstring,
}
response = requests.get(url, headers=headers2)
if response.status_code == 200:
result = response.content
# commit transaction
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
response = requests.put(url, headers=headers)
dologging(
'polar.log', 'Committing transaction on {url}'.format(url=url))
else: # pragma: no cover
result = None
return result
return None # pragma: no cover
-267
View File
@@ -1,267 +0,0 @@
from celery import Celery, app
from rowers.rower_rules import is_workout_user
import time
from django_rq import job
from rowers.tasks import handle_rp3_async_workout
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
RP3_CLIENT_ID, RP3_CLIENT_KEY, RP3_REDIRECT_URI, RP3_CLIENT_SECRET,
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET
)
from rowers.utils import myqueue, NoTokenError
# All the functionality needed to connect to Runkeeper
from rowers.imports import *
# Python
import gzip
from datetime import timedelta
import base64
from io import BytesIO
from rowers.utils import dologging
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
oauth_data = {
'client_id': RP3_CLIENT_ID,
'client_secret': RP3_CLIENT_SECRET,
'redirect_uri': RP3_REDIRECT_URI,
'autorization_uri': "https://rp3rowing-app.com/oauth/authorize?",
'content_type': 'application/x-www-form-urlencoded',
# 'content_type': 'application/json',
'tokenname': 'rp3token',
'refreshtokenname': 'rp3refreshtoken',
'expirydatename': 'rp3tokenexpirydate',
'bearer_auth': False,
'base_url': "https://rp3rowing-app.com/oauth/token",
'scope': 'read,write',
}
graphql_url = "https://rp3rowing-app.com/graphql"
# Checks if user has UnderArmour token, renews them if they are expired
def rp3_open(user):
tokenexpirydate = user.rower.rp3tokenexpirydate
if tokenexpirydate is None:
raise NoTokenError("No Token")
if tokenexpirydate is not None and timezone.now()-timedelta(days=120)>tokenexpirydate:
user.rower.rp3tokenexpirydate = timezone.now()-timedelta(days=1)
user.rower.save()
raise NoTokenError("No Token")
return imports_open(user, oauth_data)
# Refresh ST token using refresh token
def do_refresh_token(refreshtoken): # pragma: no cover
return imports_do_refresh_token(refreshtoken, oauth_data)
# Exchange access code for long-lived access token
def get_token(code): # pragma: no cover
post_data = {
"client_id": RP3_CLIENT_KEY,
"grant_type": "authorization_code",
"code": code,
"redirect_uri": RP3_REDIRECT_URI,
"client_secret": RP3_CLIENT_SECRET,
}
response = requests.post(
"https://rp3rowing-app.com/oauth/token",
data=post_data, verify=False,
)
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
except KeyError:
thetoken = 0
expires_in = 0
refresh_token = 0
return thetoken, expires_in, refresh_token
# Make authorization URL including random string
def make_authorization_url(request): # pragma: no cover
return imports_make_authorization_url(oauth_data)
def get_rp3_workout_list(user):
auth_token = rp3_open(user)
headers = {'Authorization': 'Bearer ' + auth_token}
get_workouts_list = """{
workouts{
id
executed_at
}
}"""
response = requests.post(
url=graphql_url,
headers=headers,
json={'query': get_workouts_list}
)
return response
def get_rp3_workouts(rower, do_async=True): # pragma: no cover
try:
auth_token = rp3_open(rower.user)
except NoTokenError:
return 0
res = get_rp3_workout_list(rower.user)
if (res.status_code != 200):
return 0
s = '{d}'.format(d=res.json())
dologging('rp3_import.log', s)
workouts_list = pd.json_normalize(res.json()['data']['workouts'])
try:
rp3ids = workouts_list['id'].values
workouts_list.set_index('id', inplace=True)
except (KeyError, IndexError):
return 0
knownrp3ids = uniqify([
w.uploadedtorp3 for w in Workout.objects.filter(user=rower)
])
dologging('rp3_import.log',rp3ids)
newids = [rp3id for rp3id in rp3ids if rp3id not in knownrp3ids]
dologging('rp3_import.log',newids)
for id in newids:
startdatetime = workouts_list.loc[id, 'executed_at']
dologging('rp3_import.log', startdatetime)
_ = myqueue(
queuehigh,
handle_rp3_async_workout,
rower.user.id,
auth_token,
id,
startdatetime,
20,
)
return 1
def download_rp3_file(url, auth_token, filename): # pragma: no cover
headers = {'Authorization': 'Bearer ' + auth_token}
res = requests.get(url, headers=headers)
if res.status_code == 200:
with open(filename, 'wb') as f:
f.write(res.content)
return res.status_code
def get_rp3_workout_token(workout_id, auth_token, waittime=3, max_attempts=20): # pragma: no cover
headers = {'Authorization': 'Bearer ' + auth_token}
get_download_link = """{
download(workout_id: """ + str(workout_id) + """, type:csv){
id
status
link
}
}"""
have_link = False
download_url = ''
counter = 0
while not have_link:
response = requests.post(
url=graphql_url,
headers=headers,
json={'query': get_download_link}
)
if response.status_code != 200:
have_link = True
workout_download_details = pd.json_normalize(
response.json()['data']['download'])
if workout_download_details.iat[0, 1] == 'ready':
download_url = workout_download_details.iat[0, 2]
have_link = True
counter += 1
if counter > max_attempts:
have_link = True
time.sleep(waittime)
return download_url
def get_rp3_workout_link(user, workout_id, waittime=3, max_attempts=20): # pragma: no cover
auth_token = rp3_open(user)
return get_rp3_workout_token(workout_id, auth_token, waittime=waittime, max_attempts=max_attempts)
def get_rp3_workout(user, workout_id, startdatetime=None): # pragma: no cover
url = get_rp3_workout_link(user, workout_id)
filename = 'media/RP3Import_'+str(workout_id)+'.csv'
auth_token = rp3_open(user)
if not startdatetime:
startdatetime = str(timezone.now())
status_code = download_rp3_file(url, auth_token, filename)
if status_code != 200:
return 0
userid = user.id
uploadoptions = {
'secret': UPLOAD_SERVICE_SECRET,
'user': userid,
'file': filename,
'workouttype': 'dynamic',
'boattype': '1x',
'rp3id': workout_id,
'startdatetime': startdatetime,
'timezone': str(user.rower.defaulttimezone)
}
session = requests.session()
newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'}
session.headers.update(newHeaders)
response = session.post(UPLOAD_SERVICE_URL, json=uploadoptions)
if response.status_code != 200:
return 0
return response.json()['id']
-510
View File
@@ -1,510 +0,0 @@
from rowers.tasks import handle_sporttracks_sync
from rowers.rower_rules import is_workout_user
import rowers.mytypes as mytypes
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
SPORTTRACKS_CLIENT_SECRET, SPORTTRACKS_CLIENT_ID,
SPORTTRACKS_REDIRECT_URI
)
import re
from rowers.imports import *
from rowers.utils import myqueue
# All the functionality to connect to SportTracks
import numpy
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
oauth_data = {
'client_id': SPORTTRACKS_CLIENT_ID,
'client_secret': SPORTTRACKS_CLIENT_SECRET,
'redirect_uri': SPORTTRACKS_REDIRECT_URI,
'autorization_uri': "https://api.sporttracks.mobi/oauth2/authorize",
'content_type': 'application/json',
'tokenname': 'sporttrackstoken',
'refreshtokenname': 'sporttracksrefreshtoken',
'expirydatename': 'sporttrackstokenexpirydate',
'bearer_auth': False,
'base_url': "https://api.sporttracks.mobi/oauth2/token",
'scope': 'write',
}
# Checks if user has SportTracks token, renews them if they are expired
def sporttracks_open(user):
return imports_open(user, oauth_data)
# Refresh ST token using refresh token
def do_refresh_token(refreshtoken):
return imports_do_refresh_token(refreshtoken, oauth_data)
# Exchange ST access code for long-lived ST 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)
# This is token refresh. Looks for tokens in our database, then refreshes
def rower_sporttracks_token_refresh(user): # pragma: no cover
r = Rower.objects.get(user=user)
res = do_refresh_token(r.sporttracksrefreshtoken)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
r = Rower.objects.get(user=user)
r.sporttrackstoken = access_token
r.tokenexpirydate = expirydatetime
r.sporttracksrefreshtoken = refresh_token
r.save()
return r.sporttrackstoken
# Get list of workouts available on SportTracks
def get_sporttracks_workout_list(user):
r = Rower.objects.get(user=user)
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401, s)
elif (timezone.now() > r.sporttrackstokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh."
return custom_exception_handler(401, s)
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.sporttrackstoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities"
s = requests.get(url, headers=headers)
return s
# Get workout summary data by SportTracks ID
def get_workout(user, sporttracksid, do_async=False):
r = Rower.objects.get(user=user)
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None): # pragma: no cover
return custom_exception_handler(401, s)
s = "Token doesn't exist. Need to authorize"
elif (timezone.now() > r.sporttrackstokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh."
return custom_exception_handler(401, s)
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.sporttrackstoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities/" + \
str(sporttracksid)
s = requests.get(url, headers=headers)
data = s.json()
strokedata = pd.DataFrame.from_dict({
key: pd.Series(value, dtype='object') for key, value in data.items()
})
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):
timezone = pytz.timezone(w.timezone)
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
try:
duration = w.duration.hour*3600
duration += w.duration.minute*60
duration += w.duration.second
duration += +1.0e-6*w.duration.microsecond
except AttributeError: # pragma: no cover
return 0
# 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.loc[:, 'TimeStamp (sec)'].iloc[0]
try:
t[0] = t[1]
except IndexError: # pragma: no cover
return 0
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).values
spm[0] = spm[1]
hr = row.df[' HRCur (bpm)'].astype(int).values
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:
haslatlon = 0
haspower = 1
try:
power = row.df[' Power (watts)'].astype(int).values
except KeyError: # pragma: no cover
haspower = 0
locdata = []
hrdata = []
spmdata = []
distancedata = []
powerdata = []
t = t.tolist()
hr = hr.tolist()
d = d.tolist()
spm = spm.tolist()
if haslatlon:
lat = lat.tolist()
lon = lon.tolist()
power = power.tolist()
for i in range(len(t)):
hrdata.append(t[i])
hrdata.append(hr[i])
distancedata.append(t[i])
distancedata.append(d[i])
spmdata.append(t[i])
spmdata.append(spm[i])
if haslatlon:
locdata.append(t[i])
locdata.append([lat[i], lon[i]])
if haspower:
powerdata.append(t[i])
powerdata.append(power[i])
try:
w.notes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
w.notes = 'from '+w.workoutsource+' via rowsandall.com'
st = w.startdatetime.astimezone(timezone)
st = st.replace(microsecond=0)
if haslatlon:
data = {
"type": "Rowing",
"name": w.name,
"start_time": st.isoformat(),
"total_distance": int(w.distance),
"duration": duration,
"notes": w.notes,
"avg_heartrate": averagehr,
"max_heartrate": maxhr,
"location": locdata,
"distance": distancedata,
"cadence": spmdata,
"heartrate": hrdata,
}
else:
data = {
"type": "Rowing",
"name": w.name,
"start_time": st.isoformat(),
"total_distance": int(w.distance),
"duration": duration,
"notes": w.notes,
"avg_heartrate": averagehr,
"max_heartrate": maxhr,
"distance": distancedata,
"cadence": spmdata,
"heartrate": hrdata,
}
if haspower:
data['power'] = powerdata
return data
# Obtain SportTracks Workout ID from the response returned on successful
# upload
def getidfromresponse(response): # pragma: no cover
t = response.json()
uri = t['uris'][0]
regex = '.*?sporttracks\.mobi\/api\/v2\/fitnessActivities/(\d+)\.json$'
m = re.compile(regex).match(uri).group(1)
id = int(m)
return int(id)
def default(o): # pragma: no cover
if isinstance(o, numpy.int64):
return int(o)
raise TypeError
def workout_sporttracks_upload(user, w, asynchron=False): # pragma: no cover
message = "Uploading to SportTracks"
stid = 0
# ready to upload. Hurray
thetoken = sporttracks_open(user)
if (is_workout_user(user, w)):
data = createsporttracksworkoutdata(w)
if not data:
message = "Data error"
stid = 0
return message, stid
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json"
if asynchron:
_ = myqueue(queue, handle_sporttracks_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.uploadedtosporttracks = -1
stid = -1
w.save()
return message, stid
elif (response.status_code == 201 or response.status_code == 200):
s = response.json()
stid = getidfromresponse(response)
w.uploadedtosporttracks = stid
w.save()
return 'Successfully synced to SportTracks', stid
else:
s = response
message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason
stid = 0
return message, stid
else:
message = "You are not authorized to upload this workout"
stid = 0
return message, stid
return message, stid
# Create workout from SportTracks Data, which are slightly different
# than Strava or Concept2 data
def add_workout_from_data(user, importid, data, strokedata, source='sporttracks',
workoutsource='sporttracks'):
try:
workouttype = data['type']
except KeyError: # pragma: no cover
workouttype = 'other'
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'other'
try:
comments = data['comments']
except:
comments = ''
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['start_time'])
except iso8601.ParseError: # pragma: no cover
try:
rowdatetime = datetime.datetime.strptime(
data['start_time'], "%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
try:
rowdatetime = dateutil.parser.parse(data['start_time'])
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
rowdatetime = datetime.datetime.strptime(
data['date'], "%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
starttimeunix = arrow.get(rowdatetime).timestamp()
try:
title = data['name']
except: # pragma: no cover
title = "Imported data"
try:
res = splitstdata(data['distance'])
distance = res[1]
times_distance = res[0]
except KeyError: # pragma: no cover
try:
res = splitstdata(data['heartrate'])
times_distance = res[0]
distance = 0*times_distance
except KeyError:
return (0, "No distance or heart rate data in the workout")
try:
locs = data['location']
res = splitstdata(locs)
times_location = res[0]
latlong = res[1]
latcoord = []
loncoord = []
for coord in latlong:
lat = coord[0]
lon = coord[1]
latcoord.append(lat)
loncoord.append(lon)
except:
times_location = times_distance
latcoord = np.zeros(len(times_distance))
loncoord = np.zeros(len(times_distance))
if workouttype in mytypes.otwtypes: # pragma: no cover
workouttype = 'rower'
try:
res = splitstdata(data['cadence'])
times_spm = res[0]
spm = res[1]
except KeyError: # pragma: no cover
times_spm = times_distance
spm = 0*times_distance
try:
res = splitstdata(data['heartrate'])
hr = res[1]
times_hr = res[0]
except KeyError:
times_hr = times_distance
hr = 0*times_distance
# create data series and remove duplicates
distseries = pd.Series(distance, index=times_distance)
distseries = distseries.groupby(distseries.index).first()
latseries = pd.Series(latcoord, index=times_location)
latseries = latseries.groupby(latseries.index).first()
lonseries = pd.Series(loncoord, index=times_location)
lonseries = lonseries.groupby(lonseries.index).first()
spmseries = pd.Series(spm, index=times_spm)
spmseries = spmseries.groupby(spmseries.index).first()
hrseries = pd.Series(hr, index=times_hr)
hrseries = hrseries.groupby(hrseries.index).first()
# Create dicts and big dataframe
d = {
' Horizontal (meters)': distseries,
' latitude': latseries,
' longitude': lonseries,
' Cadence (stokes/min)': spmseries,
' HRCur (bpm)': hrseries,
}
df = pd.DataFrame(d)
df = df.groupby(level=0).last()
cum_time = df.index.values
df[' ElapsedTime (sec)'] = cum_time
velo = df[' Horizontal (meters)'].diff()/df[' ElapsedTime (sec)'].diff()
df[' Power (watts)'] = 0.0*velo
nr_rows = len(velo.values)
df[' DriveLength (meters)'] = np.zeros(nr_rows)
df[' StrokeDistance (meters)'] = np.zeros(nr_rows)
df[' DriveTime (ms)'] = np.zeros(nr_rows)
df[' StrokeRecoveryTime (ms)'] = np.zeros(nr_rows)
df[' AverageDriveForce (lbs)'] = np.zeros(nr_rows)
df[' PeakDriveForce (lbs)'] = np.zeros(nr_rows)
df[' lapIdx'] = np.zeros(nr_rows)
unixtime = cum_time+starttimeunix
unixtime[0] = starttimeunix
df['TimeStamp (sec)'] = unixtime
dt = np.diff(cum_time).mean()
wsize = round(5./dt)
velo2 = ewmovingaverage(velo, wsize)
df[' Stroke500mPace (sec/500m)'] = 500./velo2
df = df.fillna(0)
df.sort_values(by='TimeStamp (sec)', ascending=True)
# csvfilename ='media/Import_'+str(importid)+'.csv'
csvfilename = 'media/{code}_{importid}.csv'.format(
importid=importid,
code=uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz', index_label='index',
compression='gzip')
id, message = dataprep.save_workout_database(csvfilename, r,
workouttype=workouttype,
title=title,
notes=comments,
dosmooth=r.dosmooth,
workoutsource='sporttracks')
return (id, message)
-404
View File
@@ -1,404 +0,0 @@
from rowers.tasks import handle_strava_sync, fetch_strava_workout
from stravalib.exc import ActivityUploadFailed, TimeoutExceeded
from rowers.rower_rules import is_workout_user, ispromember
from rowers.utils import get_strava_stream
from rowers.utils import dologging
from rowers.imports import *
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
SITE_URL
)
import gzip
import rowers.mytypes as mytypes
from rowers.utils import myqueue
from iso8601 import ParseError
import stravalib
from rowers.dataprep import columndict
# All the functionality needed to connect to Strava
from scipy import optimize
from scipy.signal import savgol_filter
import time
from time import strftime
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
try:
from json.decoder import JSONDecodeError
except ImportError: # pragma: no cover
JSONDecodeError = ValueError
webhookverification = "kudos_to_rowing"
webhooklink = SITE_URL+'/rowers/strava/webhooks/'
headers = {'Accept': 'application/json',
'Api-Key': STRAVA_CLIENT_ID,
'Content-Type': 'application/json',
'user-agent': 'sanderroosendaal'}
oauth_data = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
'redirect_uri': STRAVA_REDIRECT_URI,
'autorization_uri': "https://www.strava.com/oauth/authorize",
'content_type': 'application/json',
'tokenname': 'stravatoken',
'refreshtokenname': 'stravarefreshtoken',
'expirydatename': 'stravatokenexpirydate',
'bearer_auth': True,
'base_url': "https://www.strava.com/oauth/token",
'grant_type': 'refresh_token',
'headers': headers,
'scope': 'activity:write,activity:read_all',
}
# Exchange access code for long-lived access token
def get_token(code):
return imports_get_token(code, oauth_data)
def strava_open(user):
t = time.localtime()
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
with open('strava_open.log', 'a') as f:
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write('Getting token for user ')
f.write(str(user.rower.id))
f.write(' token expiry ')
f.write(str(user.rower.stravatokenexpirydate))
f.write(' ')
f.write(json.dumps(oauth_data))
f.write('\n')
token = imports_open(user, oauth_data)
if user.rower.strava_owner_id == 0: # pragma: no cover
_ = set_strava_athlete_id(user)
return token
def do_refresh_token(refreshtoken):
return imports_do_refresh_token(refreshtoken, oauth_data)
def rower_strava_token_refresh(user):
r = Rower.objects.get(user=user)
res = do_refresh_token(r.stravarefreshtoken)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
r.stravatoken = access_token
r.stravatokenexpirydate = expirydatetime
r.stravarefreshtoken = refresh_token
r.save()
return r.stravatoken
# Make authorization URL including random string
def make_authorization_url(request): # pragma: no cover
return imports_make_authorization_url(oauth_data)
def strava_establish_push(): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions"
post_data = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
'callback_url': webhooklink,
'verify_token': webhookverification,
}
response = requests.post(url, data=post_data)
return response.status_code
def strava_list_push(): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions"
params = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
}
response = requests.get(url, params=params)
if response.status_code == 200:
data = response.json()
return [w['id'] for w in data]
return []
def strava_push_delete(id): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions/{id}".format(id=id)
params = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
}
response = requests.delete(url, json=params)
return response.status_code
def set_strava_athlete_id(user):
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 None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate):
_ = imports_open(user, oauth_data)
authorizationstring = str('Bearer ' + r.stravatoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://www.strava.com/api/v3/athlete"
response = requests.get(url, headers=headers, params={})
if response.status_code == 200: # pragma: no cover
r.strava_owner_id = response.json()['id']
r.save()
return response.json()['id']
return 0
# Get list of workouts available on Strava
def get_strava_workout_list(user, limit_n=0):
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 None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate
): # pragma: no cover
s = "Token expired. Needs to refresh."
return custom_exception_handler(401, s)
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.stravatoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://www.strava.com/api/v3/athlete/activities"
if limit_n == 0:
params = {}
else: # pragma: no cover
params = {'per_page': limit_n}
s = requests.get(url, headers=headers, params=params)
return s
def async_get_workout(user, stravaid):
try:
_ = strava_open(user)
except NoTokenError: # pragma: no cover
return 0
csvfilename = 'media/{code}_{stravaid}.csv'.format(
code=uuid4().hex[:16], stravaid=stravaid)
job = myqueue(queue,
fetch_strava_workout,
user.rower.stravatoken,
oauth_data,
stravaid,
csvfilename,
user.id,
)
return job
# Get a Strava workout summary data and stroke data by ID
def get_workout(user, stravaid, do_async=True):
return async_get_workout(user, stravaid)
# Generate Workout data for Strava (a TCX file)
def createstravaworkoutdata(w, dozip=True):
filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except IOError: # pragma: no cover
data = dataprep.read_df_sql(w.id)
try:
datalength = len(data)
except AttributeError:
datalength = 0
if datalength != 0:
data.rename(columns=columndict, inplace=True)
_ = data.to_csv(w.csvfilename+'.gz',
index_label='index',
compression='gzip')
try:
row = rowingdata(csvfile=filename)
except IOError:
return '', 'Error - could not find rowing data'
else:
return '', 'Error - could not find rowing data'
tcxfilename = filename[:-4]+'.tcx'
try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
row.exporttotcx(tcxfilename, notes=newnotes)
if dozip:
gzfilename = tcxfilename+'.gz'
with open(tcxfilename, 'rb') as inF:
s = inF.read()
with gzip.GzipFile(gzfilename, 'wb') as outF:
outF.write(s)
try:
os.remove(tcxfilename)
except WindowError: # pragma: no cover
pass
return gzfilename, ""
return tcxfilename, ""
# Upload the TCX file to Strava and set the workout activity type
# to rowing on Strava
def handle_stravaexport(f2, workoutname, stravatoken, description='',
activity_type='Rowing', quick=False, asynchron=False):
# w = Workout.objects.get(id=workoutid)
client = stravalib.Client(access_token=stravatoken)
act = client.upload_activity(f2, 'tcx.gz', name=workoutname)
try:
if quick: # pragma: no cover
res = act.wait(poll_interval=2.0, timeout=10)
message = 'Workout successfully synchronized to Strava'
else:
res = act.wait(poll_interval=5.0, timeout=30)
message = 'Workout successfully synchronized to Strava'
except: # pragma: no cover
res = 0
message = 'Strava upload timed out'
# description doesn't work yet. Have to wait for stravalib to update
if res:
try:
act = client.update_activity(
res.id, activity_type=activity_type, description=description, device_name='Rowsandall.com')
except TypeError: # pragma: no cover
act = client.update_activity(
res.id, activity_type=activity_type, description=description)
else: # pragma: no cover
message = 'Strava activity update timed out.'
return (0, message)
return (res.id, message)
def workout_strava_upload(user, w, quick=False, asynchron=True):
try:
_ = strava_open(user)
except NoTokenError: # pragma: no cover
return "Please connect to Strava first", 0
message = "Uploading to Strava"
stravaid = -1
r = Rower.objects.get(user=user)
res = -1
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
raise NoTokenError("Your hovercraft is full of eels")
if (is_workout_user(user, w)):
if asynchron:
tcxfile, tcxmesg = createstravaworkoutdata(w)
if not tcxfile: # pragma: no cover
return "Failed to create workout data", 0
activity_type = r.stravaexportas
if r.stravaexportas == 'match':
try:
activity_type = mytypes.stravamapping[w.workouttype]
except KeyError: # pragma: no cover
activity_type = 'Rowing'
_ = myqueue(queue,
handle_strava_sync,
r.stravatoken,
w.id,
tcxfile, w.name, activity_type,
w.notes)
dologging('strava_export_log.log', 'Exporting as {t} from {w}'.format(
t=activity_type, w=w.workouttype))
return "Asynchronous sync", -1
try:
tcxfile, tcxmesg = createstravaworkoutdata(w)
if tcxfile:
activity_type = r.stravaexportas
if r.stravaexportas == 'match':
try:
activity_type = mytypes.stravamapping[w.workouttype]
except KeyError: # pragma: no cover
activity_type = 'Rowing'
with open(tcxfile, 'rb') as f:
try:
description = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
description = ' via rowsandall.com'
res, mes = handle_stravaexport(
f, w.name,
r.stravatoken,
description=description,
activity_type=activity_type, quick=quick, asynchron=asynchron)
if res == 0: # pragma: no cover
message = mes
w.uploadedtostrava = -1
stravaid = -1
w.save()
try:
os.remove(tcxfile)
except WindowsError:
pass
return message, stravaid
w.uploadedtostrava = res
w.save()
try:
os.remove(tcxfile)
except WindowsError: # pragma: no cover
pass
message = mes
stravaid = res
return message, stravaid
else: # pragma: no cover
message = "Strava TCX data error "+tcxmesg
w.uploadedtostrava = -1
stravaid = -1
w.save()
return message, stravaid
except ActivityUploadFailed as e: # pragma: no cover
message = "Strava Upload error: %s" % e
w.uploadedtostrava = -1
stravaid = -1
w.save()
os.remove(tcxfile)
return message, stravaid
return message, stravaid # pragma: no cover
+262 -2
View File
@@ -47,6 +47,8 @@ import sys
import json
import traceback
from time import strftime
import base64
from io import BytesIO
from scipy import optimize
from scipy.signal import savgol_filter
@@ -105,7 +107,8 @@ except KeyError: # pragma: no cover
NK_API_LOCATION = CFG["nk_api_location"]
TP_CLIENT_ID = CFG["tp_client_id"]
TP_CLIENT_SECRET = CFG["tp_client_secret"]
TP_API_LOCATION = CFG["tp_api_location"]
tpapilocation = TP_API_LOCATION
from requests_oauthlib import OAuth1, OAuth1Session
@@ -344,6 +347,46 @@ def handle_add_workouts_team(ws, t, debug=False, **kwargs):
return 1
def uploadactivity(access_token, filename, description='',
name='Rowsandall.com workout'):
data_gz = BytesIO()
with open(filename, 'rb') as inF:
s = inF.read()
with gzip.GzipFile(fileobj=data_gz, mode="w") as gzf:
gzf.write(s)
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer %s' % access_token
}
data = {
"UploadClient": "rowsandall",
"Filename": filename,
"SetWorkoutPublic": True,
"Title": name,
"Type": "rowing",
"Comment": description,
"Data": base64.b64encode(data_gz.getvalue()).decode("ascii")
}
resp = requests.post(tpapilocation+"/v3/file",
data=json.dumps(data),
headers=headers, verify=False)
if resp.status_code not in (200, 202): # pragma: no cover
dologging('tp_export.log',resp.status_code)
dologging('tp_export.log',resp.reason)
dologging('tp_export.log',json.dumps(data))
return 0, resp.reason, resp.status_code, headers
else:
return 1, "ok", 200, resp.headers
return 0, 0, 0, 0 # pragma: no cover
@app.task
def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs):
authorizationstring = str('Bearer ' + workout.user.tptoken)
@@ -361,6 +404,37 @@ def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs):
return 1
@app.task
def handle_workout_tp_upload(w, thetoken, tcxfilename, debug=False, **kwargs):
tpid = 0
r = w.user
if not tcxfilename:
return 0
res, reason, status_code, headers = uploadactivity(
thetoken, tcxfilename,
name=w.name
)
if res == 0:
w.tpid = -1
try:
os.remove(tcxfilename)
except WindowsError:
pass
w.save()
return 0
w.uploadedtotp = res
tpid = res
w.save()
os.remove(tcxfilename)
check_tp_workout_id(w,headers['Location'])
return tpid
@app.task
def instroke_static(w, metric, debug=False, **kwargs):
f1 = w.csvfilename[6:-4]
@@ -447,6 +521,189 @@ def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs):
return 1
def splitstdata(lijst):
t = []
latlong = []
while len(lijst) >= 2:
t.append(lijst[0])
latlong.append(lijst[1])
lijst = lijst[2:]
return [np.array(t), np.array(latlong)]
@app.task
def handle_sporttracks_workout_from_data(user, importid, source,
workoutsource, debug=False, **kwargs):
r = user.rower
authorizationstring = str('Bearer ' + r.sporttrackstoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities/" + \
str(importid)
s = requests.get(url, headers=headers)
data = s.json()
strokedata = pd.DataFrame.from_dict({
key: pd.Series(value, dtype='object') for key, value in data.items()
})
try:
workouttype = data['type']
except KeyError: # pragma: no cover
workouttype = 'other'
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'other'
try:
comments = data['comments']
except:
comments = ''
r = Rower.objects.get(user=user)
rowdatetime = iso8601.parse_date(data['start_time'])
starttimeunix = arrow.get(rowdatetime).timestamp()
try:
title = data['name']
except: # pragma: no cover
title = "Imported data"
try:
res = splitstdata(data['distance'])
distance = res[1]
times_distance = res[0]
except KeyError: # pragma: no cover
try:
res = splitstdata(data['heartrate'])
times_distance = res[0]
distance = 0*times_distance
except KeyError:
return (0, "No distance or heart rate data in the workout")
try:
locs = data['location']
res = splitstdata(locs)
times_location = res[0]
latlong = res[1]
latcoord = []
loncoord = []
for coord in latlong:
lat = coord[0]
lon = coord[1]
latcoord.append(lat)
loncoord.append(lon)
except:
times_location = times_distance
latcoord = np.zeros(len(times_distance))
loncoord = np.zeros(len(times_distance))
if workouttype in mytypes.otwtypes: # pragma: no cover
workouttype = 'rower'
try:
res = splitstdata(data['cadence'])
times_spm = res[0]
spm = res[1]
except KeyError: # pragma: no cover
times_spm = times_distance
spm = 0*times_distance
try:
res = splitstdata(data['heartrate'])
hr = res[1]
times_hr = res[0]
except KeyError:
times_hr = times_distance
hr = 0*times_distance
# create data series and remove duplicates
distseries = pd.Series(distance, index=times_distance)
distseries = distseries.groupby(distseries.index).first()
latseries = pd.Series(latcoord, index=times_location)
latseries = latseries.groupby(latseries.index).first()
lonseries = pd.Series(loncoord, index=times_location)
lonseries = lonseries.groupby(lonseries.index).first()
spmseries = pd.Series(spm, index=times_spm)
spmseries = spmseries.groupby(spmseries.index).first()
hrseries = pd.Series(hr, index=times_hr)
hrseries = hrseries.groupby(hrseries.index).first()
# Create dicts and big dataframe
d = {
' Horizontal (meters)': distseries,
' latitude': latseries,
' longitude': lonseries,
' Cadence (stokes/min)': spmseries,
' HRCur (bpm)': hrseries,
}
df = pd.DataFrame(d)
df = df.groupby(level=0).last()
cum_time = df.index.values
df[' ElapsedTime (sec)'] = cum_time
velo = df[' Horizontal (meters)'].diff()/df[' ElapsedTime (sec)'].diff()
df[' Power (watts)'] = 0.0*velo
nr_rows = len(velo.values)
df[' DriveLength (meters)'] = np.zeros(nr_rows)
df[' StrokeDistance (meters)'] = np.zeros(nr_rows)
df[' DriveTime (ms)'] = np.zeros(nr_rows)
df[' StrokeRecoveryTime (ms)'] = np.zeros(nr_rows)
df[' AverageDriveForce (lbs)'] = np.zeros(nr_rows)
df[' PeakDriveForce (lbs)'] = np.zeros(nr_rows)
df[' lapIdx'] = np.zeros(nr_rows)
unixtime = cum_time+starttimeunix
unixtime[0] = starttimeunix
df['TimeStamp (sec)'] = unixtime
dt = np.diff(cum_time).mean()
wsize = round(5./dt)
velo2 = ewmovingaverage(velo, wsize)
df[' Stroke500mPace (sec/500m)'] = 500./velo2
df = df.fillna(0)
df.sort_values(by='TimeStamp (sec)', ascending=True)
csvfilename = 'media/{code}_{importid}.csv'.format(
importid=importid,
code=uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz', index_label='index',
compression='gzip')
uploadoptions = {
'secret': UPLOAD_SERVICE_SECRET,
'user': user.id,
'file': csvfilename+'.gz',
'title': '',
'workouttype': workouttype,
'boattype': '1x',
'sporttracksid': importid,
'title':title,
}
session = requests.session()
newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'}
session.headers.update(newHeaders)
_ = session.post(UPLOAD_SERVICE_URL, json=uploadoptions)
return 1
@app.task
def handle_sporttracks_sync(workoutid, url, headers, data, debug=False, **kwargs):
@@ -2975,6 +3232,9 @@ def handle_update_wps(rid, types, ids, mode, debug=False, **kwargs):
@app.task
def handle_rp3_async_workout(userid, rp3token, rp3id, startdatetime, max_attempts, debug=False, **kwargs):
timezone = kwargs.get('timezone', 'UTC')
headers = {'Authorization': 'Bearer ' + rp3token}
get_download_link = """{
@@ -3056,6 +3316,7 @@ def handle_rp3_async_workout(userid, rp3token, rp3id, startdatetime, max_attempt
'boattype': '1x',
'rp3id': int(rp3id),
'startdatetime': startdatetime,
'timezone': timezone,
}
session = requests.session()
@@ -3086,7 +3347,6 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
data = alldata[int(nkid)]
except KeyError:
return 0
params = {
'sessionIds': nkid,
}
+95
View File
@@ -0,0 +1,95 @@
{% extends "newbase.html" %}
{% load static %}
{% load rowerfilters %}
{% block title %}Workouts{% endblock %}
{% block main %}
<h1>Available on {{ integration }}</h1>
<ul class="main-content">
<li class="grid_2">
<form enctype="multipart/form-data" method="post">
<table>
{{ dateform.as_table }}
</table>
{% csrf_token %}
<input name='daterange' type="submit" value="Select Dates">
</form>
</li>
{% if workouts %}
{% if integration == 'C2 Logbook' %}
<li class="grid_2">
<a href="/rowers/workout/c2import/all/{{ page }}">Import all NEW</a>
<p>This imports all workouts that have not been imported to rowsandall.com.
The action may take a longer time to process, so please be patient. Click on Import in the list below to import an individual workout.
</p>
</li>
<li class="grid_2">
<p>
<span>
{% if page > 1 %}
<a title="Previous" href="/rowers/workout/c2import/{{ page|add:-1 }}">
<i class="fas fa-arrow-alt-left"></i>
</a>
{% endif %}
<a title="Next" href="/rowers/workout/c2import/{{ page|add:1 }}">
<i class="fas fa-arrow-alt-right"></i>
</a>
</span>
</p>
</li>
{% endif %}
<li class="grid_4">
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
<input name='workouts' type="submit" value="Import selected workouts">
<a href="?selectallnew=true">Select All New</a>
<table width="70%" class="listtable">
<thead>
<tr>
<th> Import </th>
<th> Date/Time </th>
<th> Duration </th>
<th> Total Distance</th>
<th> Type</th>
<th> Source</th>
<th> Name</th>
<th> New</th>
</tr>
</thead>
<tbody>
{% for workout in workouts %}
<tr>
<td>
{% if workout|lookup:'new' == 'NEW' and checknew == 'true' %}
<input checked type="checkbox" value={{ workout|lookup:'id' }} name="workoutid">
{% else %}
<input type="checkbox" value={{ workout|lookup:'id' }} name="workoutid">
{% endif %}
</td>
<td>{{ workout|lookup:'starttime' }}</td>
<td>{{ workout|lookup:'duration' }}</td>
<td>{{ workout|lookup:'distance' }}</td>
<td>{{ workout|lookup:'rowtype' }}</td>
<td>{{ workout|lookup:'source' }}</td>
<td>{{ workout|lookup:'name' }}</td>
<td>
{{ workout|lookup:'new' }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
</li>
{% else %}
<p> No workouts found </p>
{% endif %}
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_workouts.html' %}
{% endblock %}
+1 -1
View File
@@ -37,7 +37,7 @@
<label for="group-1"><i class="fas fa-cloud-download fa-fw"></i>&nbsp;Import</label>
<ul>
<li id="concept2"><a href="/rowers/workout/c2list/">Concept2</a></li>
<li id="concept2"><a href="/rowers/workout/c2import/">Concept2</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>
-4
View File
@@ -234,10 +234,6 @@
<a href="/rowers/me/rp3authorize">
Connect to RP3
</a>
{% else %}
<a href="https://rp3rowing-app.com/home">
RP3
</a>
{% endif %}
</li>
<li id="export-csv">
+1 -1
View File
@@ -51,7 +51,7 @@
<label for="group-1"><i class="fas fa-cloud-download fa-fw"></i>&nbsp;Import</label>
<ul>
<li id="concept2"><a href="/rowers/workout/c2list/">Concept2</a></li>
<li id="concept2"><a href="/rowers/workout/c2import/">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="sporttracks"><a href="/rowers/workout/sporttracksimport/">SportTracks</a></li>
+5 -7
View File
@@ -24,8 +24,7 @@ from rowers.models import PlannedSession
from rowers.teams import coach_getcoachees
from rowers import credits
from rowers import c2stuff
from rowers.c2stuff import c2_open
from rowers.integrations import C2Integration
from rowers.rower_rules import *
from rowers.mytypes import (
otwtypes, adaptivetypes, sexcategories, weightcategories, workouttypes,
@@ -521,12 +520,9 @@ def deltatimeprint(d): # pragma: no cover
@register.filter
def c2userid(user): # pragma: no cover
try:
thetoken = c2_open(user)
except NoTokenError: # pragma: no cover
return 0
c2integration = C2Integration(user)
c2userid = c2stuff.get_userid(thetoken)
c2userid = c2integration.get_userid(user)
return c2userid
@@ -570,6 +566,8 @@ def lookup(dict, key):
s = dict.get(key)
except KeyError: # pragma: no cover
return None
except AttributeError:
return None
if isinstance(s, string_types) and len(s) > 22:
s = s[:22]
+2 -3
View File
@@ -18,7 +18,6 @@ from django.test import TestCase, Client,override_settings
from django.core.management import call_command
from django.test.client import RequestFactory
from rowers.views import c2_open
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage
from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail
import rowers.plots as plots
@@ -39,7 +38,7 @@ from nose.tools import assert_true
from mock import Mock, patch
#from minimocktest import MockTestCase
import pandas as pd
import rowers.c2stuff as c2stuff
import arrow
from django.http import HttpResponseRedirect
@@ -1367,7 +1366,7 @@ def mocked_requests(*args, **kwargs):
if stravatester.match(args[0]):
if stravaworkoutlisttester.match(args[0]):
return MockResponse(stravaworkoutlist,200)
+2 -3
View File
@@ -37,8 +37,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from io import StringIO
from django.test.client import RequestFactory
from rowers.views import c2_open
from rowers.integrations import *
from rowers.forms import (
DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail,
@@ -65,7 +64,7 @@ from mock import Mock, patch
#from minimocktest import MockTestCase
import pandas as pd
import rowers.c2stuff as c2stuff
import rowers.sporttracksstuff as sporttracksstuff
import rowers.rojabo_stuff as rojabo_stuff
from django.urls import reverse, reverse_lazy
+1 -1
View File
@@ -14,7 +14,7 @@ import rowers
from rowers import dataprep
from rowers import tasks
from rowers import c2stuff
from rowers import stravastuff
import urllib
import json
import pandas as pd
+13 -10
View File
@@ -11,7 +11,7 @@ nu = datetime.datetime.now()
from rowers import tasks
import rowers.courses as courses
from rowers.integrations.sporttracks import default as stdefault
class fakejob:
def __init__(self):
@@ -186,15 +186,16 @@ class AsyncTaskTests(TestCase):
self.u.id)
self.assertEqual(res,1)
@patch('rowers.c2stuff.requests.post', side_effect=mocked_requests)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
def test_c2_sync(self, mock_get, mock_post):
authorizationstring = str('Bearer aap')
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
data = c2stuff.createc2workoutdata(self.wwater)
integration = C2Integration(self.u)
data = integration.createworkoutdata(self.wwater)
url = "https://log.concept2.com/api/users/%s/results" % (1)
res = tasks.handle_c2_sync(1,url,headers,data)
@@ -452,8 +453,8 @@ class AsyncTaskTests(TestCase):
self.assertEqual(y,'3.142')
@patch('rowers.sporttracksstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.sporttracksstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.sporttracks.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.sporttracks.requests.get', side_effect=mocked_requests)
def test_sporttracks_sync(self, mock_get, mock_post):
authorizationstring = str('Bearer aap')
headers = {'Authorization': authorizationstring,
@@ -462,15 +463,17 @@ class AsyncTaskTests(TestCase):
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json"
data = sporttracksstuff.createsporttracksworkoutdata(self.wwater)
integration = SportTracksIntegration(self.u)
data = integration.createworkoutdata(self.wwater)
res = tasks.handle_sporttracks_sync(1,url,headers,json.dumps(data,default=sporttracksstuff.default))
res = tasks.handle_sporttracks_sync(1,url,headers,json.dumps(data,default=stdefault))
self.assertEqual(res,1)
@patch('rowers.c2stuff.requests.post',side_effect=mocked_requests)
@patch('rowers.c2stuff.requests.get',side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.post',side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get',side_effect=mocked_requests)
def test_import_c2_strokedata(self, mock_get, mock_post):
c2token = 'aap'
c2id = 1212
+1 -1
View File
@@ -14,7 +14,7 @@ import rowers
from rowers import dataprep
from rowers import tasks
from rowers import c2stuff
from rowers import stravastuff
import urllib
import json
+3 -4
View File
@@ -202,11 +202,10 @@ class EmailTests(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.polarstuff.get_polar_notifications')
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.c2stuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.post', side_effect=mocked_requests)
def test_emailprocessing(
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
self, mocked_sqlalchemy,mocked_get, mock_post):
out = StringIO()
call_command('processemail', stdout=out,testing=True,mailbox='workouts4')
self.assertIn('Successfully processed email attachments',out.getvalue())
+106 -134
View File
@@ -14,18 +14,17 @@ import numpy as np
import rowers
from rowers import dataprep
from rowers import tasks
from rowers import c2stuff
from rowers import stravastuff
from rowers import polarstuff
import urllib
import json
import rowers.utils as utils
import rowers.rojabo_stuff as rojabo_stuff
from rowers.integrations import *
from django.db import transaction
import rowers.garmin_stuff as gs
import rowers.integrations.strava as strava
@pytest.mark.django_db
@override_settings(TESTING=True)
@@ -312,7 +311,9 @@ class C2Objects(DjangoTestCase):
)
def test_timezone_c2(self):
data = c2stuff.createc2workoutdata(self.w)
c2integration = C2Integration(self.u)
data = c2integration.createworkoutdata(self.w)
wenddtime = self.w.startdatetime+datetime.timedelta(seconds=self.totaltime)
t1 = arrow.get(wenddtime).timestamp()
t2 = arrow.get(data['date']).timestamp()
@@ -328,38 +329,37 @@ class C2Objects(DjangoTestCase):
#self.assertEqual(data['date'],wenddtime.strftime('%Y-%m-%d %H:%M:%S'))
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.integrations.c2.Session', side_effect=mocked_requests)
def test_c2_callback(self, mock_Session):
response = self.c.get('/call_back?code=dsdoij232s',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.integrations.c2.Session', side_effect=mocked_requests)
def test_c2_token_refresh(self, mock_Session):
response = self.c.get('/rowers/me/c2refresh/',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.c2stuff.requests.post', side_effect=mocked_requests)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.c2stuff.requests.session', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.session', side_effect=mocked_requests)
def test_c2_auto_import(self, mock_get, mock_post,MockSession):
self.r.sporttracks_auto_export = True
self.r.save()
res = c2stuff.get_c2_workouts(self.r)
self.assertEqual(res,1)
c2integration = C2Integration(self.u)
res = c2integration.get_workouts()
res = c2stuff.get_c2_workouts(self.r,do_async=False)
self.assertEqual(res,1)
@patch('rowers.c2stuff.requests.post', side_effect=mocked_requests)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
def test_c2_upload(self, mock_get, mock_post):
response = self.c.get('/rowers/workout/'+encoded1+'/c2uploadw/')
url = '/rowers/workout/'+encoded1+'/c2uploadw/'
response = self.c.get(url)
self.assertRedirects(response,
expected_url = '/rowers/workout/'+encoded1+'/edit/',
status_code=302,target_status_code=200)
@@ -367,20 +367,20 @@ class C2Objects(DjangoTestCase):
self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/')
self.assertEqual(response.status_code, 302)
@patch('rowers.c2stuff.requests.post', side_effect=mocked_requests)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
def test_c2_list(self, mock_get, mock_post):
response = self.c.get('/rowers/workout/c2list',follow=True)
response = self.c.get('/rowers/workout/c2import',follow=True)
self.assertEqual(response.status_code,200)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
@patch('rowers.dataprep.create_engine')
def test_c2_import(self, mock_get, mocked_sqlalchemy):
response = self.c.get('/rowers/workout/c2import/12/',follow=True)
expected_url = reverse('workout_c2import_view')
expected_url = reverse('workout_import_view',kwargs={'source':'c2'})
self.assertRedirects(response,
expected_url=expected_url,
@@ -506,13 +506,13 @@ class C2Objects(DjangoTestCase):
self.assertEqual(got, want)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
@patch('rowers.dataprep.create_engine')
@patch('rowers.tasks.requests.session', side_effect=mocked_session)
def test_c2_import_tz(self, mock_get, mocked_sqlalchemy, mock_session):
response = self.c.get('/rowers/workout/c2import/22/',follow=True)
expected_url = '/rowers/workout/c2list/'
expected_url = '/rowers/workout/c2import/'
self.assertRedirects(response,
expected_url=expected_url,
@@ -526,12 +526,12 @@ class C2Objects(DjangoTestCase):
self.assertEqual(timezone,'Europe/Prague')
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
@patch('rowers.dataprep.create_engine')
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/'
expected_url = '/rowers/workout/c2import/'
self.assertRedirects(response,
expected_url=expected_url,
@@ -547,11 +547,11 @@ class C2Objects(DjangoTestCase):
@patch('rowers.tasks.requests.get', side_effect=mocked_requests)
@patch('rowers.dataprep.create_engine')
@patch('rowers.c2stuff.requests.session', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.session', side_effect=mocked_requests)
def test_c2_import_tz2(self, mock_get, mocked_sqlalchemy, MockSession):
response = self.c.get('/rowers/workout/c2import/31/',follow=True)
expected_url = '/rowers/workout/c2list/'
expected_url = '/rowers/workout/c2import/'
result = tasks.handle_c2_getworkout(self.r.user.id,self.r.c2token,31,self.r.defaulttimezone)
@@ -643,15 +643,15 @@ class C2ObjectsTokenExpired(DjangoTestCase):
@patch('rowers.c2stuff.requests.post', side_effect=mocked_requests)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.c2stuff.Session',side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.Session',side_effect=mocked_requests)
def test_c2_list(self, mock_get, mock_post, mock_Session):
response = self.c.get('/rowers/workout/c2list',follow=True)
response = self.c.get('/rowers/workout/c2import/',follow=True)
self.assertEqual(response.status_code,200)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.c2.requests.get', side_effect=mocked_requests)
@patch('rowers.dataprep.create_engine')
def test_c2_import(self, mock_get, mocked_sqlalchemy):
@@ -715,10 +715,12 @@ class NKObjects(DjangoTestCase):
csvfilename=filename
)
@patch('rowers.nkstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.nkstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests)
def test_nk_list(self, mock_get, mockpost):
result = rowers.nkstuff.rower_nk_token_refresh(self.u)
integration = NKIntegration(self.u)
result = integration.token_refresh()
self.assertEqual(result,"TA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq")
response = self.c.get('/rowers/workout/nkimport/')
@@ -741,39 +743,39 @@ class NKObjects(DjangoTestCase):
)
self.assertTrue(res>0)
@patch('rowers.nkstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests)
def notest_nk_callback(self, mock_post):
response = self.c.get('/nk_callback?code=absdef23&scope=read',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.nkstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.nkstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests)
def test_nk_get_workouts(self, mock_get, mockpost):
result = rowers.nkstuff.nk_open(self.u)
integration = NKIntegration(self.u)
result = integration.open()
self.assertEqual(result,"TA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq")
response = self.c.get('/rowers/workout/nkimport/all/',follow=True)
response = self.c.get('/rowers/workout/nkimport/?selectallnew=true',follow=True)
expected = reverse('workouts_view')
self.assertRedirects(response,
expected_url=expected,
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
@patch('rowers.nkstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.nkstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests)
@patch('rowers.nkimportutils.requests.session', side_effect=mocked_session)
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
def test_nk_import(self, mock_get, mock_post,
mocked_session,
mocked_getsmallrowdata_db):
result = rowers.nkstuff.rower_nk_token_refresh(self.u)
integration = NKIntegration(self.u)
result = integration.token_refresh()
response = self.c.get('/rowers/workout/nkimport/469',follow=True)
expected_url = reverse('workout_nkimport_view')
expected_url = reverse('workout_import_view',kwargs={'source':'nk'})
self.assertRedirects(response,
expected_url=expected_url,
@@ -785,18 +787,19 @@ class NKObjects(DjangoTestCase):
#self.assertEqual(w.inboard,0.89)
#self.assertEqual(w.oarlength,2.87)
@patch('rowers.nkstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.nkstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests)
@patch('rowers.nkimportutils.requests.session', side_effect=mocked_session)
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
def test_nk_import_impeller(self, mock_get, mock_post,
mocked_session,
mocked_getsmallrowdata_db):
result = rowers.nkstuff.rower_nk_token_refresh(self.u)
integration = NKIntegration(self.u)
result = integration.token_refresh()
response = self.c.get('/rowers/workout/nkimport/404',follow=True)
expected_url = reverse('workout_nkimport_view')
expected_url = reverse('workout_import_view',kwargs={'source':'nk'})
self.assertRedirects(response,
expected_url=expected_url,
@@ -871,39 +874,40 @@ class PolarObjects(DjangoTestCase):
csvfilename=filename
)
@patch('rowers.polarstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.polar.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.polar.requests.get', side_effect=mocked_requests)
def test_polar_auto_import(self, mock_get, mock_post):
self.r.polar_auto_import = True
self.r.save()
integration = PolarIntegration(self.r.user)
res = integration.get_workouts(self.r.user)
self.assertEqual(res,1)
res = polarstuff.get_polar_workouts(self.r.user)
self.assertEqual(len(res),2)
@patch('rowers.polarstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.polar.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.polar.requests.get', side_effect=mocked_requests)
def test_polar_callback(self, mock_get, mock_post):
response = self.c.get('/polarflowcallback?code=abcdef&state=12sdss',follow=True)
self.assertEqual(response.status_code,200)
@patch('rowers.polarstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.polar.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.polar.requests.get', side_effect=mocked_requests)
def test_polar_notifications(self, mock_get, mock_post):
data = polarstuff.get_polar_notifications()
integration = PolarIntegration(self.r.user)
data = integration.get_notifications()
self.assertEqual(data[0]['user-id'],475)
response = polarstuff.get_all_new_workouts(data)
response = integration.get_workouts()
self.assertEqual(response,1)
@patch('rowers.polarstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.polar.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.polar.requests.get', side_effect=mocked_requests)
def test_polar_get_workout(self, mock_get, mock_post):
transaction_id = 240522162
id = 1937529874
response = polarstuff.get_polar_workout(self.u, id, transaction_id)
integration = PolarIntegration(self.u)
response = integration.get_workout(id, transaction_id)
self.assertEqual(len(response),14836)
@@ -963,15 +967,15 @@ class RP3Objects(DjangoTestCase):
csvfilename=filename
)
@patch('rowers.rp3stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.rp3stuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.rp3.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.rp3.requests.post', side_effect=mocked_requests)
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
def test_rp3_import(self, mock_get, mockpost,
mocked_getsmallrowdata_db):
response = self.c.get('/rowers/workout/rp3import/591621',follow=True)
expected_url = reverse('workout_rp3import_view')
expected_url = reverse('workout_import_view',kwargs={'source':'rp3'})
self.assertRedirects(response,
expected_url=expected_url,
@@ -994,7 +998,7 @@ class RP3Objects(DjangoTestCase):
res = tasks.handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,max_attempts)
self.assertEqual(res,1)
@patch('rowers.rp3stuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.rp3.requests.post', side_effect=mocked_requests)
def notest_rp3_callback(self, mock_post):
response = self.c.get('/rp3_callback?code=absdef23&scope=read',follow=True)
self.assertEqual(response.status_code, 200)
@@ -1058,14 +1062,14 @@ class StravaObjects(DjangoTestCase):
csvfilename=filename,uploadedtostrava=123,
)
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
def test_strava_webhook(self, mock_get, mock_post):
url = reverse('strava_webhook_view')
params = {
'hub.challenge':'aap',
'hub.verify_token':stravastuff.webhookverification,
'hub.verify_token':strava.webhookverification,
}
url2 = url+'?'+urllib.parse.urlencode(params)
@@ -1116,21 +1120,18 @@ class StravaObjects(DjangoTestCase):
response = self.c.generic('POST', url, raw_data)
self.assertEqual(response.status_code,200)
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
@patch('rowers.stravastuff.stravalib.Client',side_effect=MockStravalibClient)
def test_workout_strava_upload(self, mock_get, mock_post,MockStravalibClient):
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
def test_workout_strava_upload(self, mock_get, mock_post):
w = Workout.objects.get(id=1)
res = stravastuff.workout_strava_upload(self.r.user,w,asynchron=True)
self.assertEqual(res[1],-1)
res = stravastuff.workout_strava_upload(self.r.user,w,asynchron=False)
integration = StravaIntegration(self.r.user)
res = integration.workout_export(w)
self.assertEqual(len(res[0]),43)
self.assertEqual(res,1)
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
@patch('rowers.stravastuff.stravalib.Client',side_effect=MockStravalibClient)
def test_strava_upload(self, mock_get, mock_post,MockStravalibClient):
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
def test_strava_upload(self, mock_get, mock_post):
response = self.c.get('/rowers/workout/'+encoded1+'/stravauploadw/')
self.assertRedirects(response,
@@ -1141,23 +1142,24 @@ class StravaObjects(DjangoTestCase):
self.assertEqual(response.status_code, 302)
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
def test_strava_list(self, mock_get, mockpost):
result = rowers.stravastuff.rower_strava_token_refresh(self.u)
integration = StravaIntegration(self.u)
result = integration.token_refresh()
self.assertEqual(result,"987654321234567898765432123456789")
response = self.c.get('/rowers/workout/stravaimport/')
self.assertEqual(response.status_code,200)
@patch('rowers.utils.requests.get', side_effect=mocked_requests)
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
@patch('rowers.dataprep.getsmallrowdata_db')
def test_strava_import(self, mock_get, mock_post,
mocked_getsmallrowdata_db):
response = self.c.get('/rowers/workout/stravaimport/12',follow=True)
expected_url = reverse('workout_stravaimport_view')
expected_url = reverse('workout_import_view',kwargs={'source':'strava'})
self.assertRedirects(response,
expected_url=expected_url,
@@ -1165,16 +1167,12 @@ class StravaObjects(DjangoTestCase):
self.assertEqual(response.status_code, 200)
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
def test_strava_callback(self, mock_post):
@patch('rowers.integrations.integrations.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
def test_strava_callback(self, mock_post, mock_get):
response = self.c.get('/stravacall_back?code=absdef23&scope=read',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
def test_strava_token_refresh(self, mock_post):
result = rowers.stravastuff.rower_strava_token_refresh(self.u)
self.assertEqual(result,"987654321234567898765432123456789")
#@pytest.mark.django_db
@@ -1234,7 +1232,7 @@ class STObjects(DjangoTestCase):
csvfilename=filename
)
@patch('rowers.sporttracksstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.sporttracks.requests.post', side_effect=mocked_requests)
def test_sporttracks_callback(self, mock_post):
response = self.c.get('/sporttracks_callback?code=dsdoij232s',follow=True)
@@ -1242,15 +1240,15 @@ class STObjects(DjangoTestCase):
self.assertEqual(response.status_code, 200)
@patch('rowers.sporttracksstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.sporttracks.requests.post', side_effect=mocked_requests)
def test_sporttracks_token_refresh(self, mock_post):
response = self.c.get('/rowers/me/sporttracksrefresh/',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.sporttracksstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.sporttracksstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.sporttracks.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.sporttracks.requests.get', side_effect=mocked_requests)
def test_sporttracks_upload(self, mock_get, mock_post):
response = self.c.get('/rowers/workout/'+encoded1+'/sporttracksuploadw/')
@@ -1261,7 +1259,7 @@ class STObjects(DjangoTestCase):
self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/')
self.assertEqual(response.status_code, 302)
@patch('rowers.sporttracksstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.sporttracks.requests.get', side_effect=mocked_requests)
def test_sporttracks_list(self, mock_get):
response = self.c.get('/rowers/workout/sporttracksimport',follow=True)
@@ -1294,36 +1292,10 @@ class STObjects(DjangoTestCase):
@patch('rowers.imports.requests.get', side_effect=mocked_requests)
def test_sporttracks_import_all(self, mock_get):
response = self.c.get('/rowers/workout/sporttracksimport/all/',follow=True)
expected_url = reverse('workouts_view')
self.assertRedirects(response,
expected_url=expected_url,
status_code=302,target_status_code=200)
response = self.c.get('/rowers/workout/sporttracksimport/?selectallnew=true')
self.assertEqual(response.status_code, 200)
@patch('rowers.dataprep.create_engine')
def test_strokedata(self, mocked_sqlalchemy):
with open('rowers/tests/testdata/sporttrackstestdata.txt','r') as infile:
data = json.load(infile)
from rowers.sporttracksstuff import add_workout_from_data
res = add_workout_from_data(self.u,1,data,data)
@patch('rowers.dataprep.create_engine')
def test_strokedatanohr(self, mocked_sqlalchemy):
with open('rowers/tests/testdata/sporttrackstestnohr.txt','r') as infile:
data = json.load(infile)
from rowers.sporttracksstuff import add_workout_from_data
res = add_workout_from_data(self.u,1,data,data)
#@pytest.mark.django_db
@@ -1391,15 +1363,15 @@ class TPObjects(DjangoTestCase):
self.assertEqual(response.status_code, 200)
@patch('rowers.tpstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.trainingpeaks.requests.post', side_effect=mocked_requests)
def test_tp_token_refresh(self, mock_post):
response = self.c.get('/rowers/me/tprefresh/',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.tpstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.tpstuff.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.trainingpeaks.requests.post', side_effect=mocked_requests)
@patch('rowers.integrations.trainingpeaks.requests.get', side_effect=mocked_requests)
def test_tp_upload(self, mock_get, mock_post):
url = '/rowers/workout/'+encoded1+'/tpuploadw/'
+9 -9
View File
@@ -214,8 +214,8 @@ class PermissionsViewTests(TestCase):
# Test access for anonymous users
@parameterized.expand(viewstotest)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.c2stuff.c2_open')
@patch('rowers.integrations.c2.Session', side_effect=mocked_requests)
@patch('rowers.integrations.c2.C2Integration.open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@@ -249,8 +249,8 @@ class PermissionsViewTests(TestCase):
# Test access for logged in users - accessing own objects
@parameterized.expand(viewstotest)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.c2stuff.c2_open')
@patch('rowers.integrations.c2.Session', side_effect=mocked_requests)
@patch('rowers.integrations.c2.C2Integration.open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@@ -331,8 +331,8 @@ class PermissionsViewTests(TestCase):
# Test access for logged in users - accessing team member objects
@parameterized.expand(viewstotest)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.c2stuff.c2_open')
@patch('rowers.integrations.c2.Session', side_effect=mocked_requests)
@patch('rowers.integrations.c2.C2Integration.open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@@ -414,8 +414,8 @@ class PermissionsViewTests(TestCase):
# Test access for logged in users - accessing coachee
@parameterized.expand(viewstotest)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.c2stuff.c2_open')
@patch('rowers.integrations.c2.Session', side_effect=mocked_requests)
@patch('rowers.integrations.c2.C2Integration.open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@@ -424,7 +424,7 @@ class PermissionsViewTests(TestCase):
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
def test_permissions_coachee(
self,view,permissions,
mock_Session,
mocked_session,
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
+5 -4
View File
@@ -17,12 +17,13 @@ import pytz
# interactive plots
from rowers import interactiveplots
from rowers import dataprep
from rowers import tasks
from rowers import plannedsessions
from rowers.views.workoutviews import get_video_id
from rowers import stravastuff
import rowingdata
from rowers.c2stuff import getagegrouprecord
class OtherUnitTests(TestCase):
def setUp(self):
@@ -120,7 +121,7 @@ class OtherUnitTests(TestCase):
s = f.read()
data = json.loads(s)
splitdata = data['workout']['intervals']
summary = c2stuff.summaryfromsplitdata(splitdata,data,'aap.txt')
summary = tasks.summaryfromsplitdata(splitdata,data,'aap.txt')
self.assertEqual(len(summary),3)
sums = summary[0]
@@ -592,7 +593,7 @@ class DataPrepTests(TestCase):
def test_getagegrouprecord(self):
records = C2WorldClassAgePerformance.objects.filter(distance=2000,sex=self.r.sex,weightcategory=self.r.weightcategory)
result = c2stuff.getagegrouprecord(25)
result = getagegrouprecord(25)
self.assertEqual(int(result),590)
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_uh)
Binary file not shown.
+2 -8
View File
@@ -84,15 +84,9 @@
95,112,WorkoutDelete,delete workout,TRUE,403,basic,200,302,basic,403,403,coach,403,403,FALSE,FALSE,TRUE,FALSE,FALSE,
96,113,workout_smoothenpace_view,smoothen pace,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
97,114,workout_undo_smoothenpace_view,unsmoothen pace,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
98,115,workout_c2import_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,
99,120,workout_stravaimport_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,
101,124,workout_getimportview,imports a workout from third party,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
103,126,workout_getstravaworkout_next,gets all strava workouts,TRUE,302,basic,302,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
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,
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,
104,127,workout_import_view,list workouts to be imported (test stops at notokenerror),TRUE,200,basic,200,302,basic,403,403,coach,403,403,FALSE,FALSE,FALSE,FALSE,FALSE,
109,135,workout_export_view,upload workout to integration,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,
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,
1 id view function anonymous anonymous_response own own_response own_nonperm member member_response member_nonperm coachee coachee_response coachee_nonperm is_staff userid workoutid dotest realtest kwargs
84 95 112 WorkoutDelete delete workout TRUE 403 basic 200 302 basic 403 403 coach 403 403 FALSE FALSE TRUE FALSE FALSE
85 96 113 workout_smoothenpace_view smoothen pace TRUE 403 pro 302 302 pro 403 403 coach 302 403 FALSE FALSE TRUE TRUE TRUE
86 97 114 workout_undo_smoothenpace_view unsmoothen pace TRUE 403 pro 302 302 pro 403 403 coach 302 403 FALSE FALSE TRUE TRUE TRUE
98 115 workout_c2import_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
99 120 workout_stravaimport_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
87 101 124 workout_getimportview imports a workout from third party TRUE 200 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
88 103 104 126 127 workout_getstravaworkout_next workout_import_view gets all strava workouts list workouts to be imported (test stops at notokenerror) TRUE 302 200 basic 302 200 302 FALSE basic 200 403 302 403 FALSE coach 200 403 302 403 FALSE FALSE FALSE FALSE FALSE
89 104 109 127 135 workout_sporttracksimport_view workout_export_view list workouts to be imported (test stops at notokenerror) upload workout to integration TRUE 302 200 basic 302 200 302 basic 403 200 403 302 coach 302 200 403 302 FALSE TRUE FALSE FALSE TRUE TRUE FALSE TRUE FALSE
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
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
90 111 137 workout_recalcsummary_view recalculates workout summary TRUE 403 basic 302 403 basic 403 403 coach 302 403 FALSE FALSE TRUE TRUE TRUE
91 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
92 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
-232
View File
@@ -1,232 +0,0 @@
from celery import Celery, app
from rowers.rower_rules import is_workout_user
import time
from django_rq import job
# All the functionality needed to connect to Runkeeper
from rowers.imports import *
from rowers.utils import dologging
from rowers.tasks import check_tp_workout_id
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
# Python
import gzip
import base64
from io import BytesIO
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
TP_CLIENT_ID, TP_CLIENT_SECRET,
TP_REDIRECT_URI, TP_CLIENT_KEY,TP_API_LOCATION,
TP_OAUTH_LOCATION,
)
tpapilocation = TP_API_LOCATION
oauth_data = {
'client_id': TP_CLIENT_ID,
'client_secret': TP_CLIENT_SECRET,
'redirect_uri': TP_REDIRECT_URI,
'autorization_uri': "https://oauth.trainingpeaks.com/oauth/authorize?",
'content_type': 'application/x-www-form-urlencoded',
# 'content_type': 'application/json',
'tokenname': 'tptoken',
'refreshtokenname': 'tprefreshtoken',
'expirydatename': 'tptokenexpirydate',
'bearer_auth': False,
'base_url': "https://oauth.trainingpeaks.com/oauth/token",
'scope': 'write',
}
# Checks if user has UnderArmour token, renews them if they are expired
def tp_open(user):
return imports_open(user, oauth_data)
# Refresh ST token using refresh token
def do_refresh_token(refreshtoken):
return imports_do_refresh_token(refreshtoken, oauth_data)
# Exchange access code for long-lived access token
def get_token(code):
# client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET)
post_data = {
"client_id": TP_CLIENT_KEY,
"grant_type": "authorization_code",
"code": code,
"redirect_uri": TP_REDIRECT_URI,
"client_secret": TP_CLIENT_SECRET,
}
response = requests.post(
TP_OAUTH_LOCATION+"/oauth/token/",
data=post_data, verify=False,
)
if response.status_code != 200:
return 0,0,0
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
except KeyError: # pragma: no cover
thetoken = 0
expires_in = 0
refresh_token = 0
return thetoken, expires_in, refresh_token
# Make authorization URL including random string
def make_authorization_url(request): # pragma: no cover
return imports_make_authorization_url(oauth_data)
def getidfromresponse(response): # pragma: no cover
t = json.loads(response.text)
links = t["_links"]
id = links["self"][0]["id"]
return int(id)
def createtpworkoutdata(w):
filename = w.csvfilename
row = rowingdata(csvfile=filename)
tcxfilename = filename[:-4]+'.tcx'
try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
row.exporttotcx(tcxfilename, notes=newnotes)
return tcxfilename
def tp_check(access_token): # pragma: no cover
headers = {
"Content-Type": "application/json",
'Accept': 'application/json',
'authorization': 'Bearer %s' % access_token
}
resp = requests.post(tpapilocation+"/v2/info/version",
headers=headers, verify=False)
return resp
def uploadactivity(access_token, filename, description='',
name='Rowsandall.com workout'):
data_gz = BytesIO()
with open(filename, 'rb') as inF:
s = inF.read()
with gzip.GzipFile(fileobj=data_gz, mode="w") as gzf:
gzf.write(s)
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer %s' % access_token
}
data = {
"UploadClient": "rowsandall",
"Filename": filename,
"SetWorkoutPublic": True,
"Title": name,
"Type": "rowing",
"Comment": description,
"Data": base64.b64encode(data_gz.getvalue()).decode("ascii")
}
#resp = requests.post(tpapilocation+"/v2/file/synchronous",
# data=json.dumps(data),
# headers=headers, verify=False)
resp = requests.post(tpapilocation+"/v3/file",
data=json.dumps(data),
headers=headers, verify=False)
if resp.status_code not in (200, 202): # pragma: no cover
dologging('tp_export.log',resp.status_code)
dologging('tp_export.log',resp.reason)
dologging('tp_export.log',json.dumps(data))
return 0, resp.reason, resp.status_code, headers
else:
return 1, "ok", 200, resp.headers
return 0, 0, 0, 0 # pragma: no cover
def workout_tp_upload(user, w): # pragma: no cover
message = "Uploading to TrainingPeaks"
tpid = 0
r = w.user
thetoken = tp_open(r.user)
# need some code if token doesn't refresh
if (is_workout_user(user, w)):
tcxfile = createtpworkoutdata(w)
if tcxfile:
res, reason, status_code, headers = uploadactivity(
thetoken, tcxfile,
name=w.name
)
if res == 0:
message = "Upload to TrainingPeaks failed with status code " + \
str(status_code)+": "+reason
w.tpid = -1
try:
os.remove(tcxfile)
except WindowsError:
pass
return message, tpid
else: # res != 0
w.uploadedtotp = res
tpid = res
w.save()
os.remove(tcxfile)
job = myqueue(queuelow,
check_tp_workout_id,
w,
headers['Location'])
return 'Successfully synchronized to TrainingPeaks', tpid
else: # no tcxfile
dologging('tp_export.log','Failed to create tcx file')
message = "Upload to TrainingPeaks failed"
w.uploadedtotp = -1
tpid = -1
w.save()
return message, tpid
else: # not allowed to upload
message = "You are not allowed to export this workout to TP"
tpid = 0
return message, tpid
return message, tpid
-1
View File
@@ -95,7 +95,6 @@ class TraverseLinksTest(TestCase):
'.*authorize.*',
'.*youtu.*',
'.*earth.*',
'.*c2list.*',
'.*stravaimport.*',
'.*performancephones.*',
'.*sporttracks.*',
+18 -16
View File
@@ -1,9 +1,8 @@
from rowers.mytypes import workouttypes, boattypes, otwtypes, workoutsources, workouttypes_ordered
import rowers.c2stuff as c2stuff
from rowers.rower_rules import is_promember
import rowers.tpstuff as tpstuff
import rowers.sporttracksstuff as sporttracksstuff
import rowers.stravastuff as stravastuff
from rowers.integrations import *
from rowers.utils import (
geo_distance, serialize_list, deserialize_list, uniqify,
str2bool, range_to_color_hex, absolute, myqueue, NoTokenError
@@ -130,7 +129,6 @@ def make_plot(r, w, f1, f2, plottype, title, imagename='', plotnr=0):
def do_sync(w, options, quick=False):
do_strava_export = w.user.strava_auto_export
try:
do_strava_export = options['upload_to_Strava'] or do_strava_export
@@ -199,9 +197,9 @@ def do_sync(w, options, quick=False):
if do_c2_export: # pragma: no cover
dologging('c2_log.log','Exporting workout to C2 for user {user}'.format(user=w.user.user.id))
c2_integration = C2Integration(w.user.user)
try:
message, id = c2stuff.workout_c2_upload(
w.user.user, w, asynchron=True)
id = c2_integration.workout_export(w)
dologging('c2_log.log','C2 upload succeeded')
except NoTokenError:
id = 0
@@ -212,10 +210,9 @@ def do_sync(w, options, quick=False):
pass
if do_strava_export: # pragma: no cover
strava_integration = StravaIntegration(w.user.user)
try:
message, id = stravastuff.workout_strava_upload(
w.user.user, w, quick=quick, asynchron=True,
)
id = strava_integration.workout_export(w)
dologging(
'strava_export_log.log',
'exporting workout {id} as {type}'.format(
@@ -236,6 +233,12 @@ def do_sync(w, options, quick=False):
f.write(str(e))
do_st_export = w.user.sporttracks_auto_export
sporttracksid = options.get('sporttracksid','')
if sporttracksid != 0 and sporttracksid != '':
w.uploadedtosporttracks = sporttracksid
w.save()
do_st_export = False
try: # pragma: no cover
upload_to_st = options['upload_to_SportTracks'] or do_st_export
do_st_export = upload_to_st
@@ -244,9 +247,9 @@ def do_sync(w, options, quick=False):
if do_st_export: # pragma: no cover
try:
message, id = sporttracksstuff.workout_sporttracks_upload(
w.user.user, w, asynchron=True,
)
st_integration = SportTracksIntegration(w.user.user)
id = st_integration.workout_export(w)
dologging('st_export.log',
'exported workout {wid} for user {uid}'.format(
wid = w.id,
@@ -266,9 +269,8 @@ def do_sync(w, options, quick=False):
upload_to_st = False
if do_tp_export:
try:
_, id = tpstuff.workout_tp_upload(
w.user.user, w
)
tp_integration = TPIntegration(w.user.user)
id = tp_integration.workout_export(w)
dologging('tp_export.log',
'exported workout {wid} for user {uid}'.format(
wid = w.id,
+8 -85
View File
@@ -618,74 +618,16 @@ urlpatterns = [
views.workout_smoothenpace_view, name='workout_smoothenpace_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/undosmoothenpace/$',
views.workout_undo_smoothenpace_view, name='workout_undo_smoothenpace_view'),
re_path(r'^workout/c2import/$', views.workout_c2import_view,
name='workout_c2import_view'),
re_path(r'^workout/c2list/$', views.workout_c2import_view,
name='workout_c2import_view'),
re_path(r'^workout/c2list/(?P<page>\d+)/$',
views.workout_c2import_view, name='workout_c2import_view'),
re_path(r'^workout/c2list/user/(?P<userid>\d+)/$',
views.workout_c2import_view, name='workout_c2import_view'),
re_path(r'^workout/c2list/(?P<page>\d+)/user/(?P<userid>\d+)/$',
views.workout_c2import_view, name='workout_c2import_view'),
re_path(r'^workout/rp3import/$', views.workout_rp3import_view,
name='workout_rp3import_view'),
re_path(r'^workout/rp3import/user/(?P<userid>\d+)/$',
views.workout_rp3import_view, name='workout_rp3import_view'),
re_path(r'^workout/stravaimport/$', views.workout_stravaimport_view,
name='workout_stravaimport_view'),
re_path(r'^session/rojaboimport/$', views.workout_rojaboimport_view,
name='workout_rojaboimport_view'),
re_path(r'^workout/stravaimport/user/(?P<userid>\d+)/$',
views.workout_stravaimport_view, name='workout_stravaimport_view'),
re_path(r'^workout/c2import/all/$', views.workout_getc2workout_all,
name='workout_getc2workout_all'),
re_path(r'^workout/c2import/all/(?P<page>\d+)/$',
views.workout_getc2workout_all, name='workout_getc2workout_all'),
re_path(r'^workout/nkimport/$', views.workout_nkimport_view,
name='workout_nkimport_view'),
re_path(r'^workout/nkimport/(?P<after>\d+)/(?P<before>\d+)/$',
views.workout_nkimport_view, name='workout_nkimport_view'),
re_path(r'^workout/nkimport/user/(?P<userid>\d+)/$',
views.workout_nkimport_view, name='workout_nkimport_view'),
re_path(r'^workout/nkimport/all/$', views.workout_getnkworkout_all,
name='workout_getnkworkout_all'),
re_path(r'^workout/nkimport/all/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',
views.workout_getnkworkout_all,
name='workout_getnkworkout_all'),
re_path(r'^workout/rp3import/(?P<externalid>\d+)/$', views.workout_getrp3importview,
name='workout_getrp3importview'),
re_path(r'^workout/rp3import/user/(?P<userid>\d+)/$',
views.workout_rp3import_view, name='workout_rp3import_view'),
re_path(r'^workout/rp3import/all/$', views.workout_getrp3workout_all,
name='workout_getrp3workout_all'),
re_path(r'^workout/(?P<source>\w+.*)import/$',
views.workout_import_view, name='workout_import_view'),
re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/$',
views.workout_getimportview, name='workout_getimportview'),
re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/async/$',
views.workout_getimportview, {'do_async': True}, name='workout_getimportview'),
# re_path(r'^workout/stravaimport/all/$',views.workout_getstravaworkout_all,name='workout_getstravaworkout_all'),
re_path(r'^workout/stravaimport/next/$', views.workout_getstravaworkout_next,
name='workout_getstravaworkout_next'),
re_path(r'^workout/sporttracksimport/$', views.workout_sporttracksimport_view,
name='workout_sporttracksimport_view'),
re_path(r'^workout/sporttracksimport/user/(?P<userid>\d+)/$',
views.workout_sporttracksimport_view, name='workout_sporttracksimport_view'),
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/(?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>[0-9A-Fa-f]+)/(?P<source>[0-9A-za-z]+)uploadw/$',
views.workout_export_view, name='workout_export_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)/tpuploadw/$',
views.workout_tp_upload_view, name='workout_tp_upload_view'),
re_path(r'^alerts/user/(?P<userid>\d+)/$',
views.alerts_view, name='alerts_view'),
re_path(r'^alerts/$', views.alerts_view, name='alerts_view'),
@@ -795,32 +737,18 @@ urlpatterns = [
re_path(r'^me/prefs/user/(?P<userid>\d+)/$',
views.rower_simpleprefs_view, name='rower_simpleprefs_view'),
re_path(r'^me/edit/(.+.*)/$', views.rower_edit_view, name='rower_edit_view'),
re_path(r'^me/c2authorize/$', views.rower_c2_authorize,
name='rower_c2_authorize'),
re_path(r'^me/nkauthorize/$', views.rower_nk_authorize,
name='rower_nk_authorize'),
re_path(r'^me/(?P<source>\w+.*)authorize', views.rower_integration_authorize,
name='rower_integration_authorize'),
re_path(r'^me/rojaboauthorize/$', views.rower_rojabo_authorize,
name='rower_rojabo_authorize'),
re_path(r'^me/polarauthorize/$', views.rower_polar_authorize,
name='rower_polar_authorize'),
re_path(r'^me/revokeapp/(?P<id>\d+)/$',
views.rower_revokeapp_view, name='rower_revokeapp_view'),
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/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/sporttracksrefresh/$', views.rower_sporttracks_token_refresh,
name='rower_sporttracks_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/(?P<source>\w+.*)refresh/$', views.rower_integration_token_refresh,
name='rower_integration_token_refresh'),
re_path(r'^me/favoritecharts/$', views.rower_favoritecharts_view,
name='rower_favoritecharts_view'),
re_path(r'^me/favoritecharts/user/(?P<userid>\d+)/$',
@@ -1169,8 +1097,3 @@ urlpatterns = [
name="braintree_webhook_view"),
]
if settings.DEBUG: # pragma: no cover
urlpatterns += [
re_path(r'^c2listug/(?P<page>\d+)/$', views.c2listdebug_view),
re_path(r'^c2listug/$', views.c2listdebug_view),
]
+118 -1145
View File
File diff suppressed because it is too large Load Diff
+6 -13
View File
@@ -1,5 +1,6 @@
import rowers.teams as teams
from rowers.serializers import RowerSerializer, WorkoutSerializer
from rowers.integrations import integrations
from rq import Queue, cancel_job
from redis import StrictRedis, Redis
from rowers.models import C2WorldClassAgePerformance
@@ -191,26 +192,18 @@ import os
import sys
import datetime
import iso8601
import rowers.c2stuff as c2stuff
import rowers.nkstuff as nkstuff
import rowers.rojabo_stuff as rojabo_stuff
from rowers.c2stuff import c2_open
from rowers.nkstuff import nk_open
from rowers.rp3stuff import rp3_open
from rowers.sporttracksstuff import sporttracks_open
from rowers.tpstuff import tp_open
from iso8601 import ParseError
import rowers.stravastuff as stravastuff
import rowers.rojabo_stuff as rojabo_stuff
import rowers.garmin_stuff as garmin_stuff
from rowers.stravastuff import strava_open
from rowers.rojabo_stuff import rojabo_open
import rowers.polarstuff as polarstuff
import rowers.sporttracksstuff as sporttracksstuff
from rowers.integrations import *
import rowers.tpstuff as tpstuff
import rowers.rp3stuff as rp3stuff
import rowers.ownapistuff as ownapistuff
from rowers.ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
from rowsandall_app.settings import (
+10 -25
View File
@@ -2566,7 +2566,7 @@ def workout_smoothenpace_view(request, id=0, message="", successmessage=""):
if 'originalvelo' not in row.df:
row.df['originalvelo'] = velo
velo2 = stravastuff.ewmovingaverage(velo, 5)
velo2 = utils.ewmovingaverage(velo, 5)
pace2 = 500./abs(velo2)
@@ -5209,6 +5209,7 @@ def workout_upload_api(request):
# sync related IDs
sporttracksid = post_data.get('sporttracksid','')
c2id = post_data.get('c2id', '')
workoutid = post_data.get('id','')
startdatetime = post_data.get('startdatetime', '')
@@ -5630,53 +5631,37 @@ def workout_upload_view(request,
# upload to C2
if (upload_to_c2): # pragma: no cover
try:
message, id = c2stuff.workout_c2_upload(request.user, w)
c2integration = C2Integration(request.user)
id = c2integration.workout_export(w)
except NoTokenError:
id = 0
message = "Something went wrong with the Concept2 sync"
if id > 1:
messages.info(request, message)
else:
messages.error(request, message)
if (upload_to_strava): # pragma: no cover
strava_integration = StravaIntegration(request.user)
try:
message, id = stravastuff.workout_strava_upload(
request.user, w,
)
id = strava_integration.workout_export(w)
except NoTokenError:
id = 0
message = "Please connect to Strava first"
if id > 1:
messages.info(request, message)
else:
messages.error(request, message)
if (upload_to_st): # pragma: no cover
st_integration = SportTracksIntegration(request.user)
try:
message, id = sporttracksstuff.workout_sporttracks_upload(
request.user, w
)
id = st_integration.workout_export(w)
except NoTokenError:
message = "Please connect to SportTracks first"
id = 0
if id > 1:
messages.info(request, message)
else:
messages.error(request, message)
if (upload_to_tp): # pragma: no cover
tp_integration = TPIntegration(request.user)
try:
message, id = tpstuff.workout_tp_upload(
request.user, w
)
id = tp_integration.workout_export(w)
except NoTokenError:
message = "Please connect to TrainingPeaks first"
id = 0
if id > 1:
messages.info(request, message)
else:
messages.error(request, message)
if int(registrationid) < 0: # pragma: no cover