Merge branch 'feature/nklink' into develop
This commit is contained in:
+2
-1
@@ -57,7 +57,7 @@ from rowingdata import (
|
||||
MysteryParser, BoatCoachOTWParser,QuiskeParser,
|
||||
painsledDesktopParser, speedcoachParser, ErgStickParser,
|
||||
SpeedCoach2Parser, FITParser, fitsummarydata,
|
||||
RitmoTimeParser,KinoMapParser,
|
||||
RitmoTimeParser,KinoMapParser,NKLiNKLogbookParser,
|
||||
make_cumvalues,cumcpdata,ExcelTemplate,
|
||||
summarydata, get_file_type,
|
||||
)
|
||||
@@ -1850,6 +1850,7 @@ parsers = {
|
||||
'ergdata': ErgDataParser,
|
||||
'humon': HumonParser,
|
||||
'eth': ETHParser,
|
||||
'nklinklogbook': NKLiNKLogbookParser,
|
||||
}
|
||||
|
||||
def parsenonpainsled(fileformat,f2,summary,startdatetime=''):
|
||||
|
||||
+2
-1
@@ -201,6 +201,7 @@ def imports_get_token(
|
||||
code,oauth_data
|
||||
):
|
||||
|
||||
|
||||
redirect_uri = oauth_data['redirect_uri']
|
||||
client_secret = oauth_data['client_secret']
|
||||
client_id = oauth_data['client_id']
|
||||
@@ -266,7 +267,7 @@ def imports_get_token(
|
||||
except (ValueError,TypeError):
|
||||
expires_in = 0
|
||||
else:
|
||||
return [0,0,0]
|
||||
return [0,response.text,0]
|
||||
|
||||
|
||||
return [thetoken,expires_in,refresh_token]
|
||||
|
||||
@@ -997,6 +997,13 @@ class Rower(models.Model):
|
||||
|
||||
rp3_auto_import = models.BooleanField(default=False)
|
||||
|
||||
nktoken = models.TextField(default='',max_length=1000,blank=True,null=True)
|
||||
nktokenexpirydate = models.DateTimeField(blank=True,null=True)
|
||||
nkrefreshtoken = models.TextField(default='',max_length=1000,
|
||||
blank=True,null=True)
|
||||
nk_owner_id = models.BigIntegerField(default=0)
|
||||
nk_auto_import = models.BooleanField(default=False)
|
||||
|
||||
trainingpeaks_auto_export = models.BooleanField(default=False)
|
||||
|
||||
polartoken = models.CharField(default='',max_length=1000,blank=True,null=True)
|
||||
@@ -3182,6 +3189,7 @@ class Workout(models.Model):
|
||||
uploadedtorunkeeper = models.BigIntegerField(default=0)
|
||||
uploadedtogarmin = models.BigIntegerField(default=0)
|
||||
uploadedtorp3 = models.BigIntegerField(default=0)
|
||||
uploadedtonk = models.BigIntegerField(default=0)
|
||||
forceunit = models.CharField(default='lbs',
|
||||
choices = (
|
||||
('lbs','lbs'),
|
||||
@@ -3261,6 +3269,7 @@ class TombStone(models.Model):
|
||||
uploadedtounderarmour = models.BigIntegerField(default=0)
|
||||
uploadedtotp = models.BigIntegerField(default=0)
|
||||
uploadedtorunkeeper = models.BigIntegerField(default=0)
|
||||
uploadedtonk = models.BigIntegerField(default=0)
|
||||
|
||||
@receiver(models.signals.pre_delete,sender=Workout)
|
||||
def create_tombstone_on_delete(sender, instance, **kwargs):
|
||||
@@ -3271,6 +3280,7 @@ def create_tombstone_on_delete(sender, instance, **kwargs):
|
||||
uploadedtounderarmour = instance.uploadedtounderarmour,
|
||||
uploadedtotp = instance.uploadedtotp,
|
||||
uploadedtorunkeeper = instance.uploadedtorunkeeper,
|
||||
uploadedtonk = instance.uploadedtonk
|
||||
)
|
||||
t.save()
|
||||
|
||||
|
||||
+289
-37
@@ -9,8 +9,8 @@ from time import strftime
|
||||
|
||||
import requests
|
||||
|
||||
#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
|
||||
#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
|
||||
|
||||
@@ -22,16 +22,20 @@ queuehigh = django_rq.get_queue('low')
|
||||
from rowers.rower_rules import is_workout_user, ispromember
|
||||
|
||||
from iso8601 import ParseError
|
||||
from rowers.utils import myqueue
|
||||
from rowers.utils import myqueue,get_nk_summary, get_nk_allstats, get_nk_intervalstats,getdict
|
||||
|
||||
import rowers.mytypes as mytypes
|
||||
import gzip
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
NK_CLIENT_ID, NK_REDIRECT_URI, NK_CLIENT_SECRET,
|
||||
SITE_URL, NK_API_LOCATION
|
||||
SITE_URL, NK_API_LOCATION,
|
||||
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET,
|
||||
)
|
||||
|
||||
from rowers.tasks import handle_nk_async_workout
|
||||
|
||||
|
||||
try:
|
||||
from json.decoder import JSONDecodeError
|
||||
except ImportError:
|
||||
@@ -53,29 +57,139 @@ oauth_data = {
|
||||
'scope':'read',
|
||||
}
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
def get_token(code):
|
||||
#client_id = oauth_data['client_id']
|
||||
#client_secret = oauth_data['client_secret']
|
||||
#base_uri = oauth_data['base_url']
|
||||
url = oauth_data['base_url']
|
||||
|
||||
#callbackuri = 'https://'+callbackuri[9:]
|
||||
#print(callbackuri)
|
||||
|
||||
#nk = OAuth2Session(client_id)
|
||||
#token = nk.fetch_token(base_uri,client_secret=client_secret,authorization_response=callbackuri)
|
||||
headers = {'Accept': 'application/json',
|
||||
#'Authorization': auth_header,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
#'user-agent': 'sanderroosendaal'
|
||||
}
|
||||
|
||||
|
||||
|
||||
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]
|
||||
|
||||
#print(token)
|
||||
#return [0,0,0]
|
||||
return imports_get_token(code, oauth_data)
|
||||
|
||||
def nk_open(user):
|
||||
t = time.localtime()
|
||||
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
|
||||
token = imports_open(user,oauth_data)
|
||||
return token
|
||||
r = Rower.objects.get(user=user)
|
||||
|
||||
if (r.nktoken == '') or (r.nktoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
raise NoTokenError("User has no token")
|
||||
else:
|
||||
if (timezone.now()>r.nktokenexpirydate):
|
||||
|
||||
thetoken = rower_nk_token_refresh(user)
|
||||
if thetoken == None:
|
||||
raise NoTokenError("User has no token")
|
||||
return thetoken
|
||||
else:
|
||||
thetoken = r.nktoken
|
||||
|
||||
return thetoken
|
||||
|
||||
def get_nk_workouts(rower, do_async=True):
|
||||
try:
|
||||
thetoken = nk_open(rower.user)
|
||||
except NoTokenError:
|
||||
return 0
|
||||
|
||||
res = get_nk_workout_list(rower.user)
|
||||
|
||||
if res.status_code != 200:
|
||||
return 0
|
||||
|
||||
nkids = [item['id'] for item in res.json()]
|
||||
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:
|
||||
pass
|
||||
|
||||
knownnkids = uniqify(knownnkids+tombstones+parkedids)
|
||||
newids = [nkid for nkid in nkids if not nkid in knownnkids]
|
||||
|
||||
newparkedids = uniqify(newids+parkedids)
|
||||
|
||||
with open('nkblocked.json','wt') as nkblocked:
|
||||
data = {'ids':newparkedids}
|
||||
json.dump(data,nkblocked)
|
||||
|
||||
counter = 0
|
||||
for nkid in newids:
|
||||
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):
|
||||
return imports_do_refresh_token(refreshtoken, oauth_data)
|
||||
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:
|
||||
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)
|
||||
@@ -101,42 +215,180 @@ def get_nk_workout_list(user,fake=False):
|
||||
if (r.nktoken == '') or (r.nktoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s)
|
||||
elif (r.nktokenexpirydate is None or timezone.now()+timedelta(seconds=3599)>r.nktokenexpirydate):
|
||||
elif (r.nktokenexpirydate is None or timezone.now()+timedelta(seconds=10)>r.nktokenexpirydate):
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401,s)
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
endTime = int(arrow.now().timestamp())*1000
|
||||
endTime = str(endTime)
|
||||
startTime = arrow.now()-timedelta(days=30)*1000
|
||||
startTime = str(int(startTime.timestamp()))
|
||||
authorizationstring = str('Bearer ' + r.nktoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
url = NK_API_LOCATION+"api/v1/sessions"
|
||||
|
||||
params = {} # start / end time
|
||||
params = {
|
||||
'startTime':startTime,
|
||||
'endTime':endTime,
|
||||
} # start / end time
|
||||
|
||||
s = requests.get(url,headers=headers,params=params)
|
||||
|
||||
|
||||
return s
|
||||
|
||||
def get_nk_workout_strokes(user,nkid):
|
||||
r = Rower.objects.get(user=user)
|
||||
#
|
||||
|
||||
if (r.nktoken == '') or (r.nktoken is None):
|
||||
return custom_exception_handler(401,s)
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
elif (timezone.now()>r.tokenexpirydate):
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401,s)
|
||||
def add_workout_from_data(user,nkid,data,strokedata,source='nk',splitdata=None,
|
||||
workoutsource='nklinklogbook'):
|
||||
|
||||
csvfilename = 'media/{code}_{nkid}.csv.gz'.format(
|
||||
nkid=nkid,
|
||||
code = uuid4().hex[:16]
|
||||
)
|
||||
|
||||
strokedata.to_csv(csvfilename, index_label='index', compression='gzip')
|
||||
|
||||
userid = user.id
|
||||
|
||||
title = data["name"]
|
||||
speedInput = data["speedInput"]
|
||||
elapsedTime = data["elapsedTime"]
|
||||
totalDistanceGps = data["totalDistanceGps"]
|
||||
totalDistanceImp = data["totalDistanceImp"]
|
||||
intervals = data["intervals"] # add intervals
|
||||
oarlockSessions = data["oarlockSessions"]
|
||||
deviceId = data["deviceId"] # you could get the firmware version
|
||||
|
||||
summary = get_nk_allstats(data,strokedata)
|
||||
|
||||
# oarlock inboard, length, boat name
|
||||
if oarlockSessions:
|
||||
oarlocksession = oarlockSessions[0] # should take seatIndex
|
||||
boatName = oarlocksession["boatName"]
|
||||
oarLength = oarlocksession["oarLength"] # cm
|
||||
oarInboardLength = oarlocksession["oarInboardLength"] # cm
|
||||
seatNumber = oarlocksession["seatNumber"]
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
boatName = ''
|
||||
oarLength = 289
|
||||
oarInboardLength = 88
|
||||
seatNumber = 1
|
||||
|
||||
workouttype = "water"
|
||||
boattype = "1x"
|
||||
|
||||
uploadoptions = {
|
||||
'secret': UPLOAD_SERVICE_SECRET,
|
||||
'user':userid,
|
||||
'file': csvfilename,
|
||||
'title': title,
|
||||
'workouttype': workouttype,
|
||||
'boattype': boattype,
|
||||
'nkid':nkid,
|
||||
'inboard': oarInboardLength/100.,
|
||||
'oarlength': oarLength/100.,
|
||||
'summary':summary,
|
||||
}
|
||||
|
||||
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,response.text
|
||||
|
||||
try:
|
||||
workoutid = response.json()['id']
|
||||
except KeyError:
|
||||
workoutid = 1
|
||||
|
||||
|
||||
# evt update workout summary
|
||||
|
||||
# return
|
||||
return workoutid,""
|
||||
|
||||
|
||||
|
||||
|
||||
def get_workout(user,nkid):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.nktoken == '') or (r.nktoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s) ,0
|
||||
elif (timezone.now()>r.nktokenexpirydate):
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401,s),0
|
||||
|
||||
params = {
|
||||
'sessionIds': nkid,
|
||||
}
|
||||
|
||||
authorizationstring = str('Bearer ' + r.nktoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://log.concept2.com/api/users/me/results/"+str(nkid)+"/strokes"
|
||||
s = requests.get(url,headers=headers)
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
return s
|
||||
#
|
||||
#def get_workout(user,nkid):
|
||||
|
||||
|
||||
# get strokes
|
||||
url = NK_API_LOCATION+"api/v1/sessions/strokes"
|
||||
response = requests.get(url,headers=headers,params=params)
|
||||
|
||||
|
||||
if response.status_code != 200:
|
||||
# error handling and logging
|
||||
return {},pd.DataFrame()
|
||||
|
||||
jsonData = response.json()
|
||||
|
||||
strokeData = jsonData[str(nkid)]
|
||||
|
||||
df = pd.DataFrame.from_dict(strokeData)
|
||||
oarlockData = df['oarlockStrokes']
|
||||
|
||||
oarlockData = oarlockData.apply(lambda x:getdict(x, seatIndex=1))
|
||||
df2 = pd.DataFrame.from_records(oarlockData.values)
|
||||
|
||||
df.set_index('timestamp')
|
||||
|
||||
if not df2.empty:
|
||||
df2.set_index('timestamp')
|
||||
df = df.merge(df2,left_index=True,right_index=True)
|
||||
df = df.rename(columns={"timestamp_x":"timestamp"})
|
||||
|
||||
df = df.drop('oarlockStrokes',axis=1)
|
||||
df.sort_values(by='timestamp',ascending=True,inplace=True)
|
||||
df.fillna(inplace=True,method='ffill')
|
||||
|
||||
# get workout data
|
||||
timestampbegin = df['timestamp'].min()
|
||||
timestampend = df['timestamp'].max()
|
||||
|
||||
url = NK_API_LOCATION+"api/v1/sessions/"
|
||||
params = {
|
||||
'startTime':timestampbegin-1,
|
||||
'endTime': timestampend+1,
|
||||
}
|
||||
response = requests.get(url, headers=headers,params=params)
|
||||
|
||||
if response.status_code != 200:
|
||||
# error handling and logging
|
||||
return {},df
|
||||
|
||||
jsondata = response.json()
|
||||
workoutdata = {}
|
||||
for w in jsondata:
|
||||
if str(w['id']) == str(nkid):
|
||||
workoutdata = w
|
||||
|
||||
return workoutdata, df
|
||||
|
||||
@@ -131,8 +131,6 @@ def strava_establish_push():
|
||||
|
||||
response = requests.post(url,data=post_data)
|
||||
|
||||
print(response.json())
|
||||
|
||||
return response.status_code
|
||||
|
||||
def strava_list_push():
|
||||
|
||||
+146
@@ -53,6 +53,7 @@ from rowsandall_app.settings import PROGRESS_CACHE_SECRET
|
||||
from rowsandall_app.settings import SETTINGS_NAME
|
||||
from rowsandall_app.settings import workoutemailbox
|
||||
from rowsandall_app.settings import UPLOAD_SERVICE_SECRET, UPLOAD_SERVICE_URL
|
||||
from rowsandall_app.settings import NK_API_LOCATION
|
||||
|
||||
from requests_oauthlib import OAuth1, OAuth1Session
|
||||
|
||||
@@ -110,6 +111,8 @@ def safetimedelta(x):
|
||||
|
||||
siteurl = SITE_URL
|
||||
|
||||
from rowers.utils import get_nk_summary, get_nk_allstats, get_nk_intervalstats,getdict
|
||||
|
||||
# testing task
|
||||
|
||||
from rowers.emails import send_template_email
|
||||
@@ -2952,6 +2955,149 @@ def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,max_attempts,de
|
||||
|
||||
return workoutid
|
||||
|
||||
@app.task
|
||||
def handle_nk_async_workout(alldata,userid,nktoken,nkid,delaysec,defaulttimezone,debug=False,**kwargs):
|
||||
time.sleep(delaysec)
|
||||
data = alldata[nkid]
|
||||
|
||||
params = {
|
||||
'sessionIds': nkid,
|
||||
}
|
||||
|
||||
authorizationstring = str('Bearer ' + nktoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
# get strokes
|
||||
url = NK_API_LOCATION+"api/v1/sessions/strokes"
|
||||
response = requests.get(url,headers=headers,params=params)
|
||||
|
||||
|
||||
if response.status_code != 200:
|
||||
# error handling and logging
|
||||
return 0
|
||||
|
||||
jsonData = response.json()
|
||||
strokeData = jsonData[str(nkid)]
|
||||
df = pd.DataFrame.from_dict(strokeData)
|
||||
oarlockData = df['oarlockStrokes']
|
||||
|
||||
oarlockData = oarlockData.apply(lambda x:getdict(x, seatIndex=1))
|
||||
df2 = pd.DataFrame.from_records(oarlockData.values)
|
||||
|
||||
df.set_index('timestamp')
|
||||
|
||||
if not df2.empty:
|
||||
df2.set_index('timestamp')
|
||||
df = df.merge(df2,left_index=True,right_index=True)
|
||||
df = df.rename(columns={"timestamp_x":"timestamp"})
|
||||
|
||||
df = df.drop('oarlockStrokes',axis=1)
|
||||
df.sort_values(by='timestamp',ascending=True,inplace=True)
|
||||
df.fillna(inplace=True,method='ffill')
|
||||
|
||||
# get workout data
|
||||
timestampbegin = df['timestamp'].min()
|
||||
timestampend = df['timestamp'].max()
|
||||
|
||||
csvfilename = 'media/{code}_{nkid}.csv.gz'.format(
|
||||
nkid=nkid,
|
||||
code = uuid4().hex[:16]
|
||||
)
|
||||
|
||||
df.to_csv(csvfilename, index_label='index', compression='gzip')
|
||||
|
||||
|
||||
title = data["name"]
|
||||
speedInput = data["speedInput"]
|
||||
elapsedTime = data["elapsedTime"]
|
||||
totalDistanceGps = data["totalDistanceGps"]
|
||||
totalDistanceImp = data["totalDistanceImp"]
|
||||
intervals = data["intervals"] # add intervals
|
||||
oarlockSessions = data["oarlockSessions"]
|
||||
deviceId = data["deviceId"] # you could get the firmware version
|
||||
|
||||
summary = get_nk_allstats(data,df)
|
||||
|
||||
# oarlock inboard, length, boat name
|
||||
if oarlockSessions:
|
||||
oarlocksession = oarlockSessions[0] # should take seatIndex
|
||||
boatName = oarlocksession["boatName"]
|
||||
oarLength = oarlocksession["oarLength"] # cm
|
||||
oarInboardLength = oarlocksession["oarInboardLength"] # cm
|
||||
seatNumber = oarlocksession["seatNumber"]
|
||||
else:
|
||||
boatName = ''
|
||||
oarLength = 289
|
||||
oarInboardLength = 88
|
||||
seatNumber = 1
|
||||
|
||||
workouttype = "water"
|
||||
boattype = "1x"
|
||||
|
||||
uploadoptions = {
|
||||
'secret': UPLOAD_SERVICE_SECRET,
|
||||
'user':userid,
|
||||
'file': csvfilename,
|
||||
'title': title,
|
||||
'workouttype': workouttype,
|
||||
'boattype': boattype,
|
||||
'nkid':nkid,
|
||||
'inboard': oarInboardLength/100.,
|
||||
'oarlength': oarLength/100.,
|
||||
'summary':summary,
|
||||
}
|
||||
|
||||
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,response.text
|
||||
|
||||
try:
|
||||
workoutid = response.json()['id']
|
||||
except KeyError:
|
||||
workoutid = 1
|
||||
|
||||
if debug:
|
||||
engine = create_engine(database_url_debug, echo=False)
|
||||
else:
|
||||
engine = create_engine(database_url, echo=False)
|
||||
|
||||
query = 'SELECT uploadedtonk from rowers_workout WHERE id ={workoutid}'.format(workoutid=workoutid)
|
||||
|
||||
newnkid = 0
|
||||
with engine.connect() as conn, conn.begin():
|
||||
result = conn.execute(query)
|
||||
tdata = result.fetchall()
|
||||
if tdata:
|
||||
newnkid = tdata[0][0]
|
||||
|
||||
conn.close()
|
||||
|
||||
parkedids = []
|
||||
with open('nkblocked.json','r') as nkblocked:
|
||||
jsondata = json.load(nkblocked)
|
||||
parkedids = jsondata['ids']
|
||||
|
||||
newparkedids = [id for id in parkedids if id != newnkid]
|
||||
with open('nkblocked.json','wt') as nkblocked:
|
||||
tdata = {'ids':newparkedids}
|
||||
nkblocked.seek(0)
|
||||
json.dump(tdata,nkblocked)
|
||||
|
||||
# evt update workout summary
|
||||
|
||||
# return
|
||||
return workoutid
|
||||
|
||||
|
||||
|
||||
@app.task
|
||||
def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone,debug=False,**kwargs):
|
||||
time.sleep(delaysec)
|
||||
|
||||
@@ -155,6 +155,13 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li id="export-nk">
|
||||
{% if workout.uploadedtonk %}
|
||||
<a href="{{ workout|nkviewerlink }}">
|
||||
NK LiNK <i class="fas fa-check"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li id="export-strava">
|
||||
{% if workout.uploadedtostrava %}
|
||||
<a href="https://www.strava.com/activities/{{ workout.uploadedtostrava }}">
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
<ul>
|
||||
<li id="concept2"><a href="/rowers/workout/c2list/">Concept2</a></li>
|
||||
<li id="nklink"><a href="/rowers/workout/nkimport/">NK LiNK</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>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% block title %}Workouts{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h1>Available on nk</h1>
|
||||
<h1>Available on NK LiNK Logbook</h1>
|
||||
{% if workouts %}
|
||||
<ul class="main-content">
|
||||
<li>
|
||||
@@ -34,8 +34,8 @@
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/rowers/workout/nkimport/{{ workout|lookup:'id' }}/">Import</a></td>
|
||||
<td>{{ workout|lookup:'name' }}</td>
|
||||
<td>{{ workout|lookup:'starttime' }}</td>
|
||||
<td>{{ workout|lookuplong:'name' }}</td>
|
||||
<td class="block" style="width:auto">{{ workout|lookuplong:'starttime' }}</td>
|
||||
<td>{{ workout|lookup:'duration' }} </td>
|
||||
<td>{{ workout|lookup:'distance' }} m</td>
|
||||
<td>{{ workout|lookup:'new' }}</td>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
{% if rower.c2token is not None and rower.c2token != '' %}
|
||||
Concept2 Logbook,
|
||||
{% endif %}
|
||||
{% if rower.nktoken is not None and rower.nktoken != '' %}
|
||||
NK LiNK,
|
||||
{% endif %}
|
||||
{% if rower.sporttrackstoken is not None and rower.sporttrackstoken != '' %}
|
||||
SportTracks,
|
||||
{% endif %}
|
||||
@@ -71,6 +74,7 @@
|
||||
choice or to renew the authorization.</p>
|
||||
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
|
||||
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
|
||||
<p><a href="/rowers/me/nkauthorize/"><img src="/static/img/NKLiNK.jpg" alt="connect with NK LiNK" width="120"></a></p>
|
||||
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
|
||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
|
||||
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
|
||||
|
||||
@@ -30,6 +30,8 @@ from rowers.utils import NoTokenError, step_to_string
|
||||
|
||||
import rowers.payments as payments
|
||||
|
||||
from rowsandall_app.settings import NK_VIEWER_LOCATION
|
||||
|
||||
|
||||
from rowers.opaque import encoder
|
||||
from rowers.plannedsessions import ps_dict_get_description_html
|
||||
@@ -106,6 +108,15 @@ def adaptive(s):
|
||||
|
||||
return u
|
||||
|
||||
@register.filter
|
||||
def nkviewerlink(workout):
|
||||
url = "{nkviewer}{nkid}".format(
|
||||
nkid=workout.uploadedtonk,
|
||||
nkviewer=NK_VIEWER_LOCATION)
|
||||
|
||||
return url
|
||||
|
||||
|
||||
@register.filter
|
||||
def boatclass(s):
|
||||
u = s
|
||||
|
||||
@@ -101,6 +101,30 @@ def mock_c2open(*args, **kwargs):
|
||||
print('mock')
|
||||
return('aap')
|
||||
|
||||
def mocked_session(*args, **kwargs):
|
||||
|
||||
class MockEngine:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.headers = MockHeaders()
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return MockResponse({},200)
|
||||
|
||||
class MockHeaders:
|
||||
def update(*args, **kwargs):
|
||||
return None
|
||||
|
||||
class MockResponse:
|
||||
def __init__(self, json_data, status_code):
|
||||
self.json_data = json_data
|
||||
self.status_code = status_code
|
||||
self.ok = True
|
||||
|
||||
def json(self):
|
||||
return self.json_data
|
||||
|
||||
return MockEngine()
|
||||
|
||||
def mocked_sqlalchemy(*args, **kwargs):
|
||||
# return object with method
|
||||
|
||||
@@ -593,6 +617,12 @@ def mocked_requests(*args, **kwargs):
|
||||
with open('rowers/tests/testdata/c2jsonstrokedata.txt','r') as infile:
|
||||
c2strokedata = json.load(infile)
|
||||
|
||||
with open('rowers/tests/testdata/nk_strokes.json','r') as infile:
|
||||
nkstrokedata = json.load(infile)
|
||||
|
||||
with open('rowers/tests/testdata/nk_list.json','r') as infile:
|
||||
nkworkoutlist = json.load(infile)
|
||||
|
||||
polar_json = {
|
||||
'available-user-data': []
|
||||
}
|
||||
@@ -621,6 +651,8 @@ def mocked_requests(*args, **kwargs):
|
||||
stravaworkoutlist = json.load(open('rowers/tests/testdata/stravaworkoutlist.txt'))
|
||||
sporttracksworkoutlist = json.load(open('rowers/tests/testdata/sporttracksworkouts.txt'))
|
||||
|
||||
#nkworkoutlist = json.load(open('rowers/tests/testdata/nkworkouts.txt'))
|
||||
|
||||
rkworkoutlistjson = json.load(open('rowers/tests/testdata/rkworkoutslist.txt','r'))
|
||||
uaworkoutlistjson = json.load(open('rowers/tests/testdata/uaworkoutlist.txt','r'))
|
||||
|
||||
@@ -732,6 +764,7 @@ def mocked_requests(*args, **kwargs):
|
||||
rktester = re.compile('.*?runkeeper\.com')
|
||||
uatester = re.compile('.*?mapmyfitness\.com')
|
||||
tptester = re.compile('.*?trainingpeaks\.com')
|
||||
nktester = re.compile('.*?nkrowlink\.com')
|
||||
garmintester = re.compile('.*?garmin\.com')
|
||||
|
||||
c2importregex = '.*?concept2.com\/api\/users\/me\/results\/\d+'
|
||||
@@ -746,6 +779,12 @@ def mocked_requests(*args, **kwargs):
|
||||
c2workoutlistregex = '.*?concept2\.com\/api\/users\/me\/results\?page=\d'
|
||||
c2workoutlisttester = re.compile(c2workoutlistregex)
|
||||
|
||||
nkworkoutlistregex = '.*?nkrowlink\.com\/api\/v1\/sessions'
|
||||
nkworkoutlisttester = re.compile(nkworkoutlistregex)
|
||||
|
||||
nkstrokesregex = '.*?nkrowlink\.com\/api\/v1\/sessions\/strokes'
|
||||
nkstrokestester = re.compile(nkstrokesregex)
|
||||
|
||||
stravaathleteregex = '.*?strava\.com\/api\/v3\/athlete$'
|
||||
stravaathletetester = re.compile(stravaathleteregex)
|
||||
|
||||
@@ -796,6 +835,7 @@ def mocked_requests(*args, **kwargs):
|
||||
garmindownloadregex = '.*?garmin\.com\/mockfile?id=1'
|
||||
garmindownloadtester = re.compile(garmindownloadregex)
|
||||
|
||||
|
||||
if garmintester.match(args[0]):
|
||||
if garmindownloadtester.match(args[0]):
|
||||
return MockStreamResponse('rowers/tests/testdata/3x250m.fit',200)
|
||||
@@ -910,6 +950,24 @@ def mocked_requests(*args, **kwargs):
|
||||
return MockResponse(sporttracksworkoutlist,200)
|
||||
|
||||
|
||||
if nktester.match(args[0]):
|
||||
|
||||
if 'token' in args[0]:
|
||||
json_data = {
|
||||
'access_token': 'TA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq',
|
||||
'expires_in': 604800,
|
||||
'refresh_token': 'jHJhFzCfOOKB8oyiayubhLAlxaMkG3ruC1E8YxaR'
|
||||
}
|
||||
return MockResponse(json_data,200)
|
||||
if nkstrokestester.match(args[0]):
|
||||
|
||||
return MockResponse(nkstrokedata,200)
|
||||
if nkworkoutlisttester.match(args[0]):
|
||||
|
||||
return MockResponse(nkworkoutlist,200)
|
||||
|
||||
|
||||
|
||||
if stravatester.match(args[0]):
|
||||
if stravaworkoutlisttester.match(args[0]):
|
||||
return MockResponse(stravaworkoutlist,200)
|
||||
|
||||
@@ -360,6 +360,87 @@ class C2ObjectsTokenExpired(DjangoTestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
#@pytest.mark.django_db
|
||||
@override_settings(TESTING=True)
|
||||
class NKObjects(DjangoTestCase):
|
||||
def setUp(self):
|
||||
self.c = Client()
|
||||
self.u = User.objects.create_user('john',
|
||||
'sander@ds.ds',
|
||||
'koeinsloot')
|
||||
|
||||
self.u.first_name = 'John'
|
||||
self.u.last_name = 'Sander'
|
||||
self.u.save()
|
||||
self.r = Rower.objects.create(user=self.u,gdproptin=True,surveydone=True,
|
||||
gdproptindate=timezone.now()
|
||||
)
|
||||
|
||||
self.r.nktoken = '12'
|
||||
self.r.nkrefreshtoken = '123'
|
||||
self.r.nktokenexpirydate = datetime.datetime.now()-datetime.timedelta(days=1)
|
||||
|
||||
self.r.save()
|
||||
self.c.login(username='john',password='koeinsloot')
|
||||
self.nu = datetime.datetime.now()
|
||||
|
||||
filename = 'rowers/tests/testdata/testdata.csv'
|
||||
|
||||
rr = rrower(hrmax=self.r.max,hrut2=self.r.ut2,
|
||||
hrut1=self.r.ut1,hrat=self.r.at,
|
||||
hrtr=self.r.tr,hran=self.r.an,ftp=self.r.ftp)
|
||||
row = rdata(filename,rower=rr)
|
||||
totaldist = row.df['cum_dist'].max()
|
||||
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
|
||||
totaltime = totaltime+row.df.loc[:,' ElapsedTime (sec)'].iloc[0]
|
||||
|
||||
|
||||
hours = int(totaltime/3600.)
|
||||
minutes = int((totaltime - 3600.*hours)/60.)
|
||||
seconds = int(totaltime - 3600.*hours - 60.*minutes)
|
||||
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
|
||||
|
||||
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
|
||||
|
||||
|
||||
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
|
||||
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
|
||||
|
||||
self.w = Workout.objects.create(
|
||||
name='testworkout',workouttype='water',
|
||||
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
|
||||
starttime=workoutstarttime,
|
||||
startdatetime=row.rowdatetime,
|
||||
duration=duration,distance=totaldist,
|
||||
csvfilename=filename
|
||||
)
|
||||
|
||||
@patch('rowers.nkstuff.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.nkstuff.requests.post', side_effect=mocked_requests)
|
||||
def test_nk_list(self, mock_get, mockpost):
|
||||
result = rowers.nkstuff.rower_nk_token_refresh(self.u)
|
||||
self.assertEqual(result,"TA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq")
|
||||
response = self.c.get('/rowers/workout/nkimport/')
|
||||
|
||||
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.nkstuff.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)
|
||||
response = self.c.get('/rowers/workout/nkimport/469',follow=True)
|
||||
|
||||
self.assertRedirects(response,
|
||||
expected_url='/rowers/workout/'+encoded1+'/edit/',
|
||||
status_code=301,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
|
||||
#@pytest.mark.django_db
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "JustGo-7189M",
|
||||
"type": 0,
|
||||
"speedInput": 0,
|
||||
"startTime": 1614264826,
|
||||
"endTime": 1614269826,
|
||||
"location": "Some Location",
|
||||
"deviceId": 11,
|
||||
"elapsedTime": 1140000,
|
||||
"totalDistanceImp": 2920.69,
|
||||
"totalDistanceGps": 2286.5,
|
||||
"avgPaceImp": 195159.36302723,
|
||||
"avgPaceGps": 250466.812537141,
|
||||
"avgStrokeRate": 19.5,
|
||||
"distStrokeImp": 8.51,
|
||||
"distStrokeGps": 6.77,
|
||||
"avgHeartRate": 158,
|
||||
"totalStrokeCount": 343,
|
||||
"totalCalories": 4959900,
|
||||
"avgCalHour": 4482062.35252774,
|
||||
"avgSpeedGps": 2,
|
||||
"avgSpeedImp": 2.56,
|
||||
"avgPower": 68.6979270660324,
|
||||
"avgCatch": -46.7961491475831,
|
||||
"avgSlip": 16.1616225246003,
|
||||
"avgFinish": 48.1512290049444,
|
||||
"avgWash": 22.9514686031976,
|
||||
"avgForceAvg": 114.502194258813,
|
||||
"avgWork": 210.785493336831,
|
||||
"avgForceMax": 249.481436977143,
|
||||
"avgMaxForceAngle": 4.75378911974861,
|
||||
"startGpsLat": 39.7356346,
|
||||
"startGpsLon": -75.5581928,
|
||||
"intervals": [
|
||||
{
|
||||
"id": 1,
|
||||
"sessionId": 1,
|
||||
"startTime": 1614264826,
|
||||
"intervalNumber": 1,
|
||||
"sessionStrokeStartIndex": 473,
|
||||
"sessionStrokeEndIndex": 674,
|
||||
"sessionStrokeCount": 91,
|
||||
"elapsedTime": 1140000,
|
||||
"totalDistanceImp": 2920.69,
|
||||
"totalDistanceGps": 2286.5,
|
||||
"avgPaceImp": 195159.36302723,
|
||||
"avgPaceGps": 250466.812537141,
|
||||
"avgStrokeRate": 19.5,
|
||||
"distStrokeImp": 8.51,
|
||||
"distStrokeGps": 6.77,
|
||||
"avgHeartRate": 158,
|
||||
"totalStrokeCount": 343,
|
||||
"totalCalories": 4959900,
|
||||
"avgCalHour": 4482062.35252774,
|
||||
"avgSpeedGps": 2,
|
||||
"avgSpeedImp": 2.56,
|
||||
"avgPower": 68.6979270660324,
|
||||
"avgCatch": -46.7961491475831,
|
||||
"avgSlip": 16.1616225246003,
|
||||
"avgFinish": 48.1512290049444,
|
||||
"avgWash": 22.9514686031976,
|
||||
"avgForceAvg": 114.502194258813,
|
||||
"avgWork": 210.785493336831,
|
||||
"avgForceMax": 249.481436977143,
|
||||
"avgMaxForceAngle": 4.75378911974861,
|
||||
"startGpsLat": 39.7356346,
|
||||
"startGpsLon": -75.5581928
|
||||
}
|
||||
],
|
||||
"oarlockSessions": [
|
||||
{
|
||||
"id": 1,
|
||||
"sessionId": 1,
|
||||
"boatName": "Fast Boat",
|
||||
"seatNumber": 3,
|
||||
"portStarboard": 0,
|
||||
"oarLength": 284,
|
||||
"oarInboardLength": 85
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -541,6 +541,13 @@ def do_sync(w,options, quick=False):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if options['nkid'] != 0 and options['nkid'] != '':
|
||||
w.uploadedtonk = options['nkid']
|
||||
w.save()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if options['garminid'] != 0 and options['garminid'] != '':
|
||||
w.uploadedtogarmin = options['garminid']
|
||||
|
||||
@@ -567,6 +567,8 @@ urlpatterns = [
|
||||
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/all/$',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/all/$',views.workout_getrp3workout_all,name='workout_getrp3workout_all'),
|
||||
@@ -671,6 +673,7 @@ urlpatterns = [
|
||||
re_path(r'^me/preferences/user/(?P<userid>\d+)/$',views.rower_prefs_view,name='rower_prefs_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/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'),
|
||||
|
||||
+139
@@ -1092,3 +1092,142 @@ def step_to_string(step,short=False):
|
||||
s = 'Repeat {duration}'.format(duration=duration)
|
||||
|
||||
return s,type, nr, repeatID, repeatValue
|
||||
|
||||
def strfdelta(tdelta):
|
||||
try:
|
||||
minutes, seconds = divmod(tdelta.seconds, 60)
|
||||
tenths = int(tdelta.microseconds / 1e5)
|
||||
except AttributeError:
|
||||
minutes, seconds = divmod(tdelta.view(np.int64), 60e9)
|
||||
seconds, rest = divmod(seconds, 1e9)
|
||||
tenths = int(rest / 1e8)
|
||||
res = "{minutes:0>2}:{seconds:0>2}.{tenths:0>1}".format(
|
||||
minutes=minutes,
|
||||
seconds=seconds,
|
||||
tenths=tenths,
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def get_nk_intervalstats(workoutdata,strokedata):
|
||||
intervals = workoutdata['intervals']
|
||||
separator = "|"
|
||||
stri = "Workout Details\n"
|
||||
stri += "#-{sep}SDist{sep}-Split-{sep}-SPace-{sep}-Pwr-{sep}-SPM--{sep}-AvgHR-{sep}DPS-\n".format(
|
||||
sep=separator)
|
||||
|
||||
i = 0
|
||||
|
||||
for interval in intervals:
|
||||
id = interval['id']
|
||||
sdist = interval['totalDistanceGps']
|
||||
avgpace = interval['avgPaceGps']/1000.
|
||||
avgpacetd = timedelta(seconds=avgpace)
|
||||
newpacestring = strfdelta(avgpacetd)
|
||||
|
||||
elapsedSeconds = interval['elapsedTime']/1000. # secs
|
||||
elapsedTime = datetime.datetime.utcfromtimestamp(elapsedSeconds)
|
||||
|
||||
newsplitstring = "%s.%i" % (elapsedTime.strftime("%M:%S"), elapsedTime.microsecond/100000)
|
||||
|
||||
pwr = interval['avgPower']
|
||||
avghr = interval['avgHeartRate']
|
||||
spm = interval['avgStrokeRate']
|
||||
dps = interval['distStrokeGps']
|
||||
|
||||
stri += "{i:0>2}{sep}{sdist:0>5}{sep}{split}{sep}{space}{sep} {pwr:0>3} {sep}".format(
|
||||
i=i + 1,
|
||||
sdist=int(float(sdist)),
|
||||
split=newsplitstring,
|
||||
space=newpacestring,
|
||||
pwr=int(pwr),
|
||||
sep=separator,
|
||||
)
|
||||
stri += " {spm} {sep} {avghr:0>3} {sep}{dps:0>4.1f}\n".format(
|
||||
sep=separator,
|
||||
avghr=avghr,
|
||||
spm=spm,
|
||||
dps=dps,
|
||||
)
|
||||
|
||||
i += 1
|
||||
|
||||
return stri
|
||||
|
||||
def get_nk_summary(workoutdata,strokedata):
|
||||
|
||||
name = workoutdata['name']
|
||||
|
||||
avgpace = workoutdata['avgPaceGps']/1000. # secs
|
||||
avgpacetd = timedelta(seconds=avgpace)
|
||||
avgpacestring = strfdelta(avgpacetd)
|
||||
|
||||
elapsedSeconds = workoutdata['elapsedTime']/1000. # secs
|
||||
elapsedTime = datetime.datetime.utcfromtimestamp(elapsedSeconds)
|
||||
|
||||
timestring = "%s.%i" % (elapsedTime.strftime("%H:%M:%S"), elapsedTime.microsecond/100000)
|
||||
|
||||
dist = workoutdata['totalDistanceGps']
|
||||
spm = workoutdata['avgStrokeRate']
|
||||
avghr = workoutdata['avgHeartRate']
|
||||
avgdps = workoutdata['distStrokeGps']
|
||||
maxhr = strokedata['heartRate'].max()
|
||||
pwr = workoutdata['avgPower']
|
||||
|
||||
sep = "|"
|
||||
|
||||
|
||||
stri1 = "Workout Summary - " + name + "\n"
|
||||
stri1 += "--{sep}Total{sep}--Total---{sep}--Avg--{sep}-Avg-{sep}-Avg--{sep}-Avg-{sep}-Max-{sep}-Avg\n".format(
|
||||
sep=sep)
|
||||
stri1 += "--{sep}Dist-{sep}--Time----{sep}-Pace--{sep}-Pwr-{sep}-SPM--{sep}-HR--{sep}-HR--{sep}-DPS\n".format(
|
||||
sep=sep)
|
||||
|
||||
|
||||
|
||||
stri1 += "--{sep}{dist:0>5.0f}{sep}".format(
|
||||
sep=sep,
|
||||
dist=dist,
|
||||
)
|
||||
|
||||
stri1 += timestring + sep + avgpacestring
|
||||
|
||||
stri1 += "{sep}{avgpower:0>5.1f}".format(
|
||||
sep=sep,
|
||||
avgpower=pwr,
|
||||
)
|
||||
|
||||
stri1 += "{sep} {avgsr:2.1f} {sep}{avghr:0>5.1f}{sep}".format(
|
||||
avgsr=spm,
|
||||
sep=sep,
|
||||
avghr=avghr
|
||||
)
|
||||
|
||||
stri1 += "{maxhr:0>5.1f}{sep}{avgdps:0>4.1f}\n".format(
|
||||
sep=sep,
|
||||
maxhr=maxhr,
|
||||
avgdps=avgdps
|
||||
)
|
||||
|
||||
return stri1
|
||||
|
||||
|
||||
|
||||
return stri1
|
||||
|
||||
def get_nk_allstats(data,workoutdata):
|
||||
stri = get_nk_summary(data, workoutdata) + \
|
||||
get_nk_intervalstats(data, workoutdata)
|
||||
return stri
|
||||
|
||||
#def get_workout(user,nkid):
|
||||
def getdict(x, seatIndex=1):
|
||||
seatStrokes = pd.DataFrame(x)
|
||||
try:
|
||||
seatStrokes = seatStrokes.set_index('seatIndex')
|
||||
return dict(seatStrokes.loc[seatIndex])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return {}
|
||||
|
||||
+166
-4
@@ -392,6 +392,27 @@ def workout_sporttracks_upload_view(request,id=0):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# NK LiNK authorization
|
||||
@login_required()
|
||||
def rower_nk_authorize(request):
|
||||
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 = "https://oauth-stage.nkrowlink.com/oauth/authorize?"+urllib.parse.urlencode(params)
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
# Concept2 authorization
|
||||
@login_required()
|
||||
def rower_c2_authorize(request):
|
||||
@@ -660,7 +681,7 @@ def rower_process_callback(request):
|
||||
message = "The resource owner or authorization server denied the request"
|
||||
messages.error(request,message)
|
||||
|
||||
url = reverse('workouts_view')
|
||||
url = reverse('rower_exportsettings_view')
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@@ -670,7 +691,7 @@ def rower_process_callback(request):
|
||||
message += ' Contact info@rowsandall.com if this behavior persists.'
|
||||
messages.error(request,message)
|
||||
|
||||
url = reverse('workouts_view')
|
||||
url = reverse('rower_exportsettings_view')
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@@ -756,11 +777,150 @@ def rower_process_garmincallback(request):
|
||||
@login_required()
|
||||
def rower_process_nkcallback(request):
|
||||
# do stuff
|
||||
try:
|
||||
code = request.GET.get('code',None)
|
||||
res = nkstuff.get_token(code)
|
||||
except MultiValueDictKeyError:
|
||||
message = "The resource owner or authorization server denied the request"
|
||||
messages.error(request,message)
|
||||
|
||||
url = reverse('rower_exportsettings_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
access_token = res[0]
|
||||
if access_token == 0:
|
||||
message = res[1]
|
||||
message += ' Contact support@rowsandall.com if this behavior persists'
|
||||
messages.error(request,message)
|
||||
|
||||
url = reverse('rower_exportsettings_view')
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
expires_in = res[1]
|
||||
refresh_token = res[2]
|
||||
nk_owner_id = res[3]
|
||||
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
|
||||
|
||||
r = getrower(request.user)
|
||||
r.nktoken = access_token
|
||||
r.nktokenexpirydate = expirydatetime
|
||||
r.nkrefreshtoken = refresh_token
|
||||
r.nk_owner_id = nk_owner_id
|
||||
|
||||
r.save()
|
||||
|
||||
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
|
||||
messages.info(request,successmessage)
|
||||
url = reverse('rower_exportsettings_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@login_required()
|
||||
def workout_getnkworkout_all(request):
|
||||
try:
|
||||
thetoken = nk_open(request.user)
|
||||
except NoTokenError:
|
||||
return HttpResponseRedirect("rower_nk_authorize")
|
||||
|
||||
r = getrequestrower(request)
|
||||
|
||||
result = nkstuff.get_nk_workouts(r,do_async=True)
|
||||
|
||||
if result:
|
||||
messages.info(request,"Your NK workouts will be imported in the coming few minutes")
|
||||
else:
|
||||
messages.error(request,"Your NK workouts import failed")
|
||||
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@login_required()
|
||||
@permission_required('rower.is_coach',fn=get_user_by_userid, raise_exception=True)
|
||||
def workout_nkimport_view(request,userid=0):
|
||||
r = getrequestrower(request,userid=userid)
|
||||
try:
|
||||
thetoken = nk_open(request.user)
|
||||
except NoTokenError:
|
||||
return HttpResponseRedirect("/rowers/me/nkauthorize/")
|
||||
|
||||
res = nkstuff.get_nk_workout_list(request.user)
|
||||
|
||||
if (res.status_code != 200):
|
||||
if (res.status_code == 401):
|
||||
r = getrower(request.user)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return HttpResponseRedirect("/rowers/me/nkauthorize/")
|
||||
message = "Something went wrong in workout_nkimport_view"
|
||||
messages.error(request,message)
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# get NK IDs
|
||||
nkids = [item['id'] for item in res.json()]
|
||||
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:
|
||||
jsondata = json.load(nkblocked)
|
||||
parkedids = jsondata['ids']
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
knownnkids = uniqify(knownnkids+tombstones+parkedids)
|
||||
newids = [nkid for nkid in nkids if not nkid in knownnkids]
|
||||
|
||||
|
||||
nkdata = [{
|
||||
'id':int(item['id']),
|
||||
'elapsed_time':item['elapsedTime'],
|
||||
'start_date':arrow.get(item['startTime']),
|
||||
} for item in res.json()]
|
||||
|
||||
workouts = []
|
||||
|
||||
for item in res.json():
|
||||
d = int(float(item['totalDistanceGps'])) # could also be Impeller
|
||||
i = item['id']
|
||||
n = item['name']
|
||||
if i in knownnkids:
|
||||
nnn = ''
|
||||
else:
|
||||
nnn = 'NEW'
|
||||
ttot = str(datetime.timedelta(seconds=int(float(item['elapsedTime'])/1000.)))
|
||||
s = arrow.get(item['startTime'],tzinfo=r.defaulttimezone).format(arrow.FORMAT_RFC850)
|
||||
#s = arrow.get(item['startTime']).to(r.defaulttimezone).isoformat()
|
||||
keys = ['id','distance','duration','starttime','name','new']
|
||||
values = [i,d,ttot,s,n,nnn]
|
||||
res = dict(zip(keys, values))
|
||||
workouts.append(res)
|
||||
|
||||
workouts = workouts[::-1]
|
||||
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':'/rowers/list-workouts/',
|
||||
'name':'Workouts'
|
||||
},
|
||||
{
|
||||
'url':reverse('workout_nkimport_view'),
|
||||
'name':'Strava'
|
||||
},
|
||||
]
|
||||
|
||||
return render(request,'nk_list_import.html',
|
||||
{
|
||||
'workouts':workouts,
|
||||
'rower':r,
|
||||
'active':'nav-workouts',
|
||||
'breadcrumbs':breadcrumbs,
|
||||
'teams':get_my_teams(request.user)
|
||||
})
|
||||
|
||||
# Process Strava Callback
|
||||
@login_required()
|
||||
@@ -1811,7 +1971,8 @@ importsources = {
|
||||
'runkeeper':runkeeperstuff,
|
||||
'sporttracks':sporttracksstuff,
|
||||
'trainingpeaks':tpstuff,
|
||||
'underarmour':underarmourstuff
|
||||
'underarmour':underarmourstuff,
|
||||
'nk':nkstuff,
|
||||
}
|
||||
|
||||
@login_required()
|
||||
@@ -1840,7 +2001,7 @@ def workout_getrp3importview(request,externalid):
|
||||
def workout_getimportview(request,externalid,source = 'c2'):
|
||||
data,strokedata = importsources[source].get_workout(request.user,externalid)
|
||||
if not data:
|
||||
messages.error(request,res[1])
|
||||
messages.error(request,"No strokedata received")
|
||||
url = reverse('workouts_view')
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
@@ -1924,6 +2085,7 @@ def workout_getimportview(request,externalid,source = 'c2'):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# strokedata not empty - continue
|
||||
id,message = importsources[source].add_workout_from_data(
|
||||
request.user,
|
||||
|
||||
@@ -154,7 +154,9 @@ import os,sys
|
||||
import datetime
|
||||
import iso8601
|
||||
import rowers.c2stuff as c2stuff
|
||||
import rowers.nkstuff as nkstuff
|
||||
from rowers.c2stuff import c2_open
|
||||
from rowers.nkstuff import nk_open
|
||||
from rowers.rp3stuff import rp3_open
|
||||
from rowers.runkeeperstuff import runkeeper_open
|
||||
from rowers.sporttracksstuff import sporttracks_open
|
||||
@@ -185,7 +187,8 @@ from rowsandall_app.settings import (
|
||||
RP3_CLIENT_ID,RP3_REDIRECT_URI,RP3_CLIENT_KEY,RP3_CLIENT_SECRET,
|
||||
BRAINTREE_MERCHANT_ID,BRAINTREE_PUBLIC_KEY,BRAINTREE_PRIVATE_KEY,
|
||||
PAYMENT_PROCESSING_ON,
|
||||
RECAPTCHA_SITE_KEY, RECAPTCHA_SITE_SECRET
|
||||
RECAPTCHA_SITE_KEY, RECAPTCHA_SITE_SECRET,
|
||||
NK_REDIRECT_URI, NK_CLIENT_ID, NK_CLIENT_SECRET
|
||||
)
|
||||
|
||||
#from rowers.tasks_standalone import addcomment2
|
||||
|
||||
@@ -4870,6 +4870,7 @@ def workout_upload_api(request):
|
||||
q = request.POST
|
||||
post_data = {k: q.getlist(k) if len(q.getlist(k))>1 else v for k, v in q.items()}
|
||||
|
||||
|
||||
# only allow local host
|
||||
hostt = request.get_host().split(':')
|
||||
if hostt[0] not in ['localhost','127.0.0.1','dev.rowsandall.com','rowsandall.com']:
|
||||
@@ -4916,6 +4917,12 @@ def workout_upload_api(request):
|
||||
except KeyError:
|
||||
c2id = ''
|
||||
|
||||
try:
|
||||
nkid = post_data['nkid']
|
||||
except KeyError:
|
||||
nkid = ''
|
||||
|
||||
|
||||
try:
|
||||
rp3id = post_data['rp3id']
|
||||
except KeyError:
|
||||
@@ -5026,6 +5033,21 @@ def workout_upload_api(request):
|
||||
plottype = r.staticchartonupload
|
||||
res, jobid = uploads.make_plot(r,w,f1,f2,plottype,t)
|
||||
|
||||
try:
|
||||
oarlength = post_data['oarlength']
|
||||
inboard = post_data['inboard']
|
||||
w.inboard = inboard
|
||||
w.oarlength = oarlength
|
||||
w.save()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
summary = post_data['summary']
|
||||
w.summary = summary
|
||||
w.save()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
uploads.do_sync(w,post_data,quick=True)
|
||||
|
||||
|
||||
@@ -329,6 +329,11 @@ RP3_REDIRECT_URI = CFG["rp3_redict_uri"]
|
||||
RP3_CLIENT_KEY = RP3_CLIENT_ID
|
||||
|
||||
# Nielsen-Kellerman
|
||||
NK_CLIENT_ID = CFG["nk_client_id"]
|
||||
NK_CLIENT_SECRET = CFG["nk_client_secret"]
|
||||
NK_REDIRECT_URI = CFG["nk_redirect_uri"]
|
||||
NK_API_LOCATION = CFG["nk_api_location"]
|
||||
NK_VIEWER_LOCATION = CFG["nk_viewer_location"]
|
||||
|
||||
# Full Site URL
|
||||
SITE_URL = CFG['site_url']
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
Reference in New Issue
Block a user