sporttracks, not fully tested
This commit is contained in:
@@ -71,7 +71,7 @@ class SyncIntegration(metaclass=ABCMeta):
|
|||||||
"scope": self.oauth_data['scope'],
|
"scope": self.oauth_data['scope'],
|
||||||
"state": state}
|
"state": state}
|
||||||
|
|
||||||
url = self.oauth_data['authorizaton_uri']+urllib.parse.urlencode(params)
|
url = self.oauth_data['authorization_uri']+urllib.parse.urlencode(params)
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from .integrations import SyncIntegration, NoTokenError
|
from .integrations import SyncIntegration, NoTokenError
|
||||||
from rowers.models import User, Rower, Workout, TombStone
|
from rowers.models import User, Rower, Workout, TombStone
|
||||||
|
|
||||||
from rowers.tasks import handle_sporttracks_sync
|
from rowingdata import rowingdata
|
||||||
|
|
||||||
|
from rowers.tasks import handle_sporttracks_sync, handle_sporttracks_workout_from_data
|
||||||
from rowers.rower_rules import is_workout_user
|
from rowers.rower_rules import is_workout_user
|
||||||
import rowers.mytypes as mytypes
|
import rowers.mytypes as mytypes
|
||||||
from rowsandall_app.settings import (
|
from rowsandall_app.settings import (
|
||||||
@@ -11,13 +13,38 @@ from rowsandall_app.settings import (
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import numpy
|
import numpy
|
||||||
|
import pytz
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import datetime
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
import django_rq
|
import django_rq
|
||||||
queue = django_rq.get_queue('default')
|
queue = django_rq.get_queue('default')
|
||||||
queuelow = django_rq.get_queue('low')
|
queuelow = django_rq.get_queue('low')
|
||||||
queuehigh = django_rq.get_queue('high')
|
queuehigh = django_rq.get_queue('high')
|
||||||
|
|
||||||
from rowers.utils import myqueue, dologging
|
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):
|
class SportTracksIntegration(SyncIntegration):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -27,7 +54,7 @@ class SportTracksIntegration(SyncIntegration):
|
|||||||
'client_id': SPORTTRACKS_CLIENT_ID,
|
'client_id': SPORTTRACKS_CLIENT_ID,
|
||||||
'client_secret': SPORTTRACKS_CLIENT_SECRET,
|
'client_secret': SPORTTRACKS_CLIENT_SECRET,
|
||||||
'redirect_uri': SPORTTRACKS_REDIRECT_URI,
|
'redirect_uri': SPORTTRACKS_REDIRECT_URI,
|
||||||
'autorization_uri': "https://api.sporttracks.mobi/oauth2/authorize",
|
'authorization_uri': "https://api.sporttracks.mobi/oauth2/authorize",
|
||||||
'content_type': 'application/json',
|
'content_type': 'application/json',
|
||||||
'tokenname': 'sporttrackstoken',
|
'tokenname': 'sporttrackstoken',
|
||||||
'refreshtokenname': 'sporttracksrefreshtoken',
|
'refreshtokenname': 'sporttracksrefreshtoken',
|
||||||
@@ -42,13 +69,178 @@ class SportTracksIntegration(SyncIntegration):
|
|||||||
return super(SportTracksIntegration, self).open(*args, **kwargs)
|
return super(SportTracksIntegration, self).open(*args, **kwargs)
|
||||||
|
|
||||||
def createworkoutdata(self, w, *args, **kwargs):
|
def createworkoutdata(self, w, *args, **kwargs):
|
||||||
return None
|
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:
|
def workout_export(self, workout, *args, **kwargs) -> str:
|
||||||
pass
|
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:
|
def get_workouts(self, *args, **kwargs) -> int:
|
||||||
pass
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -57,33 +249,19 @@ class SportTracksIntegration(SyncIntegration):
|
|||||||
|
|
||||||
r = self.rower
|
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/" + \
|
|
||||||
str(sporttracksid)
|
|
||||||
s = requests.get(url, headers=headers)
|
|
||||||
|
|
||||||
data = s.json()
|
job = myqueue(
|
||||||
|
|
||||||
strokedata = pd.DataFrame.from_dict({
|
|
||||||
key: pd.Series(value, dtype='object') for key, value in data.items()
|
|
||||||
})
|
|
||||||
|
|
||||||
id = myqueue(
|
|
||||||
queue,
|
queue,
|
||||||
handle_sporttracks_workout_from_data,
|
handle_sporttracks_workout_from_data,
|
||||||
self.user,
|
self.user,
|
||||||
sporttracksid, data,
|
id,
|
||||||
strokedata,
|
|
||||||
'sporttracks',
|
'sporttracks',
|
||||||
'sporttracks'
|
'sporttracks'
|
||||||
)
|
)
|
||||||
return id
|
|
||||||
|
|
||||||
|
return job.id
|
||||||
|
|
||||||
def get_workout_list(self, *args, **kwargs) -> list:
|
def get_workout_list_json(self, *args, **kwargs) -> dict:
|
||||||
_ = self.open()
|
_ = self.open()
|
||||||
r = self.rower
|
r = self.rower
|
||||||
|
|
||||||
@@ -98,13 +276,18 @@ class SportTracksIntegration(SyncIntegration):
|
|||||||
s = "Token doesn't exist. Need to authorize"
|
s = "Token doesn't exist. Need to authorize"
|
||||||
raise NoTokenError(s)
|
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 = []
|
workouts = []
|
||||||
|
|
||||||
knownstids = uniqify([
|
knownstids = uniqify([
|
||||||
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
|
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
|
||||||
])
|
])
|
||||||
for item in res.json()['items']:
|
for item in workouts_json['items']:
|
||||||
d = int(float(item['total_distance']))
|
d = int(float(item['total_distance']))
|
||||||
i = int(getidfromuri(item['uri']))
|
i = int(getidfromuri(item['uri']))
|
||||||
if i in knownstids: # pragma: no cover
|
if i in knownstids: # pragma: no cover
|
||||||
@@ -127,7 +310,7 @@ class SportTracksIntegration(SyncIntegration):
|
|||||||
return super(SportTracksIntegration, self).make_authorization_url(*args, **kwargs)
|
return super(SportTracksIntegration, self).make_authorization_url(*args, **kwargs)
|
||||||
|
|
||||||
def get_token(self, code, *args, **kwargs) -> (str, int, str):
|
def get_token(self, code, *args, **kwargs) -> (str, int, str):
|
||||||
return ""
|
return super(SportTracksIntegration, self).get_token(code, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,350 +0,0 @@
|
|||||||
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 (
|
|
||||||
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= myqueue(
|
|
||||||
queue,
|
|
||||||
handle_sporttracks_workout_from_data,
|
|
||||||
user,
|
|
||||||
sporttracksid, data,
|
|
||||||
strokedata,
|
|
||||||
'sporttracks',
|
|
||||||
'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
|
|
||||||
|
|
||||||
@@ -458,9 +458,24 @@ def splitstdata(lijst):
|
|||||||
return [np.array(t), np.array(latlong)]
|
return [np.array(t), np.array(latlong)]
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sporttracks_workout_from_data(user, importid, data, strokedata, source,
|
def handle_sporttracks_workout_from_data(user, importid, source,
|
||||||
workoutsource, debug=False, **kwargs):
|
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:
|
try:
|
||||||
workouttype = data['type']
|
workouttype = data['type']
|
||||||
except KeyError: # pragma: no cover
|
except KeyError: # pragma: no cover
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ from mock import Mock, patch
|
|||||||
#from minimocktest import MockTestCase
|
#from minimocktest import MockTestCase
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import rowers.c2stuff as c2stuff
|
import rowers.c2stuff as c2stuff
|
||||||
import rowers.sporttracksstuff as sporttracksstuff
|
|
||||||
import rowers.rojabo_stuff as rojabo_stuff
|
import rowers.rojabo_stuff as rojabo_stuff
|
||||||
|
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
|
|||||||
@@ -1236,7 +1236,7 @@ class STObjects(DjangoTestCase):
|
|||||||
csvfilename=filename
|
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):
|
def test_sporttracks_callback(self, mock_post):
|
||||||
response = self.c.get('/sporttracks_callback?code=dsdoij232s',follow=True)
|
response = self.c.get('/sporttracks_callback?code=dsdoij232s',follow=True)
|
||||||
|
|
||||||
@@ -1244,15 +1244,15 @@ class STObjects(DjangoTestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
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):
|
def test_sporttracks_token_refresh(self, mock_post):
|
||||||
response = self.c.get('/rowers/me/sporttracksrefresh/',follow=True)
|
response = self.c.get('/rowers/me/sporttracksrefresh/',follow=True)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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)
|
||||||
@patch('rowers.sporttracksstuff.requests.get', side_effect=mocked_requests)
|
@patch('rowers.integrations.sporttracks.requests.get', side_effect=mocked_requests)
|
||||||
def test_sporttracks_upload(self, mock_get, mock_post):
|
def test_sporttracks_upload(self, mock_get, mock_post):
|
||||||
response = self.c.get('/rowers/workout/'+encoded1+'/sporttracksuploadw/')
|
response = self.c.get('/rowers/workout/'+encoded1+'/sporttracksuploadw/')
|
||||||
|
|
||||||
@@ -1263,7 +1263,7 @@ class STObjects(DjangoTestCase):
|
|||||||
self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/')
|
self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/')
|
||||||
self.assertEqual(response.status_code, 302)
|
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):
|
def test_sporttracks_list(self, mock_get):
|
||||||
response = self.c.get('/rowers/workout/sporttracksimport',follow=True)
|
response = self.c.get('/rowers/workout/sporttracksimport',follow=True)
|
||||||
|
|
||||||
@@ -1306,26 +1306,6 @@ class STObjects(DjangoTestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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
|
#@pytest.mark.django_db
|
||||||
|
|||||||
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
Binary file not shown.
@@ -2,7 +2,6 @@ from rowers.mytypes import workouttypes, boattypes, otwtypes, workoutsources, wo
|
|||||||
|
|
||||||
from rowers.rower_rules import is_promember
|
from rowers.rower_rules import is_promember
|
||||||
import rowers.tpstuff as tpstuff
|
import rowers.tpstuff as tpstuff
|
||||||
import rowers.sporttracksstuff as sporttracksstuff
|
|
||||||
|
|
||||||
from rowers.integrations import *
|
from rowers.integrations import *
|
||||||
from rowers.utils import (
|
from rowers.utils import (
|
||||||
@@ -131,7 +130,6 @@ def make_plot(r, w, f1, f2, plottype, title, imagename='', plotnr=0):
|
|||||||
|
|
||||||
|
|
||||||
def do_sync(w, options, quick=False):
|
def do_sync(w, options, quick=False):
|
||||||
|
|
||||||
do_strava_export = w.user.strava_auto_export
|
do_strava_export = w.user.strava_auto_export
|
||||||
try:
|
try:
|
||||||
do_strava_export = options['upload_to_Strava'] or do_strava_export
|
do_strava_export = options['upload_to_Strava'] or do_strava_export
|
||||||
@@ -236,8 +234,10 @@ def do_sync(w, options, quick=False):
|
|||||||
f.write(str(e))
|
f.write(str(e))
|
||||||
|
|
||||||
do_st_export = w.user.sporttracks_auto_export
|
do_st_export = w.user.sporttracks_auto_export
|
||||||
|
|
||||||
if options['sporttracksid'] != 0 and options['sporttracksid'] != '':
|
if options['sporttracksid'] != 0 and options['sporttracksid'] != '':
|
||||||
w.uploadedtost = options['sporttracksid']
|
w.uploadedtosporttracks = options['sporttracksid']
|
||||||
|
|
||||||
w.save()
|
w.save()
|
||||||
do_st_export = False
|
do_st_export = False
|
||||||
try: # pragma: no cover
|
try: # pragma: no cover
|
||||||
@@ -248,9 +248,9 @@ def do_sync(w, options, quick=False):
|
|||||||
|
|
||||||
if do_st_export: # pragma: no cover
|
if do_st_export: # pragma: no cover
|
||||||
try:
|
try:
|
||||||
message, id = sporttracksstuff.workout_sporttracks_upload(
|
st_integration = SportTracksIntegration(w.user.user)
|
||||||
w.user.user, w, asynchron=True,
|
id = st_integration.workout_export(w)
|
||||||
)
|
|
||||||
dologging('st_export.log',
|
dologging('st_export.log',
|
||||||
'exported workout {wid} for user {uid}'.format(
|
'exported workout {wid} for user {uid}'.format(
|
||||||
wid = w.id,
|
wid = w.id,
|
||||||
|
|||||||
@@ -138,13 +138,13 @@ def workout_c2_upload_view(request, id=0):
|
|||||||
# Upload workout to SportTracks
|
# Upload workout to SportTracks
|
||||||
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid)
|
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid)
|
||||||
def workout_sporttracks_upload_view(request, id=0):
|
def workout_sporttracks_upload_view(request, id=0):
|
||||||
message = ""
|
st_integration = SportTracksIntegration(request.user)
|
||||||
|
|
||||||
# ready to upload. Hurray
|
# ready to upload. Hurray
|
||||||
w = get_workout(id)
|
w = get_workout(id)
|
||||||
r = w.user
|
r = w.user
|
||||||
|
|
||||||
message, res = sporttracksstuff.workout_sporttracks_upload(
|
id = st_integration.workout_export(w)
|
||||||
r.user, w, asynchron=True)
|
|
||||||
|
|
||||||
messages.info(
|
messages.info(
|
||||||
request, 'Your workout will be synchronized with SportTracks in the background')
|
request, 'Your workout will be synchronized with SportTracks in the background')
|
||||||
@@ -240,16 +240,8 @@ def rower_polar_authorize(request): # pragma: no cover
|
|||||||
def rower_sporttracks_authorize(request): # pragma: no cover
|
def rower_sporttracks_authorize(request): # pragma: no cover
|
||||||
# Generate a random string for the state parameter
|
# Generate a random string for the state parameter
|
||||||
# Save it for use later to prevent xsrf attacks
|
# Save it for use later to prevent xsrf attacks
|
||||||
|
st_integration = SportTracksIntegration(request.user)
|
||||||
state = str(uuid4())
|
url = st_integration.make_authorization_url()
|
||||||
|
|
||||||
params = {"client_id": SPORTTRACKS_CLIENT_ID,
|
|
||||||
"response_type": "code",
|
|
||||||
"state": state,
|
|
||||||
"redirect_uri": SPORTTRACKS_REDIRECT_URI}
|
|
||||||
|
|
||||||
url = "https://api.sporttracks.mobi/oauth2/authorize?" + \
|
|
||||||
urllib.parse.urlencode(params)
|
|
||||||
|
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
@@ -335,24 +327,8 @@ def rower_tp_token_refresh(request):
|
|||||||
# SportTracks token refresh. URL for manual refresh. Not visible to users
|
# SportTracks token refresh. URL for manual refresh. Not visible to users
|
||||||
@login_required()
|
@login_required()
|
||||||
def rower_sporttracks_token_refresh(request):
|
def rower_sporttracks_token_refresh(request):
|
||||||
r = getrower(request.user)
|
st_integration = SportTracksIntegration(request.user)
|
||||||
res = sporttracksstuff.do_refresh_token(
|
result = st_integration.token_refresh()
|
||||||
r.sporttracksrefreshtoken,
|
|
||||||
)
|
|
||||||
access_token = res[0]
|
|
||||||
expires_in = res[1]
|
|
||||||
refresh_token = res[2]
|
|
||||||
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
|
|
||||||
|
|
||||||
r = getrower(request.user)
|
|
||||||
r.sporttrackstoken = access_token
|
|
||||||
r.sporttrackstokenexpirydate = expirydatetime
|
|
||||||
r.sporttracksrefreshtoken = refresh_token
|
|
||||||
|
|
||||||
r.save()
|
|
||||||
|
|
||||||
successmessage = "Tokens refreshed. Good to go"
|
|
||||||
messages.info(request, successmessage)
|
|
||||||
|
|
||||||
url = reverse('workouts_view')
|
url = reverse('workouts_view')
|
||||||
|
|
||||||
@@ -725,20 +701,8 @@ def rower_process_sporttrackscallback(request):
|
|||||||
url = reverse('rower_exportsettings_view')
|
url = reverse('rower_exportsettings_view')
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
res = sporttracksstuff.get_token(code)
|
st_integration = SportTracksIntegration(request.user)
|
||||||
|
token = st_integration.get_token(code)
|
||||||
access_token = res[0]
|
|
||||||
expires_in = res[1]
|
|
||||||
refresh_token = res[2]
|
|
||||||
|
|
||||||
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
|
|
||||||
|
|
||||||
r = getrower(request.user)
|
|
||||||
r.sporttrackstoken = access_token
|
|
||||||
r.sporttrackstokenexpirydate = expirydatetime
|
|
||||||
r.sporttracksrefreshtoken = refresh_token
|
|
||||||
|
|
||||||
r.save()
|
|
||||||
|
|
||||||
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
|
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
|
||||||
messages.info(request, successmessage)
|
messages.info(request, successmessage)
|
||||||
@@ -1100,17 +1064,8 @@ def workout_stravaimport_view(request, message="", userid=0):
|
|||||||
alldata = {}
|
alldata = {}
|
||||||
|
|
||||||
for stravaid in stravaids:
|
for stravaid in stravaids:
|
||||||
csvfilename = 'media/{code}_{stravaid}.csv'.format(
|
res = strava_integration.get_workout(id)
|
||||||
code=uuid4().hex[:16], stravaid=stravaid)
|
|
||||||
_ = myqueue(
|
|
||||||
queue,
|
|
||||||
fetch_strava_workout,
|
|
||||||
r.stravatoken,
|
|
||||||
strava_integration.oauth_data,
|
|
||||||
stravaid,
|
|
||||||
csvfilename,
|
|
||||||
r.user.id
|
|
||||||
)
|
|
||||||
# done, redirect to workouts list
|
# done, redirect to workouts list
|
||||||
messages.info(request,
|
messages.info(request,
|
||||||
'Your Strava workouts will be imported in the background.'
|
'Your Strava workouts will be imported in the background.'
|
||||||
@@ -1416,54 +1371,34 @@ def workout_polarimport_view(request, userid=0): # pragma: no cover
|
|||||||
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
|
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
|
||||||
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
||||||
def workout_sporttracksimport_view(request, message="", userid=0):
|
def workout_sporttracksimport_view(request, message="", userid=0):
|
||||||
r = getrequestrower(request, userid=userid)
|
st_integration = SportTracksIntegration(request.user)
|
||||||
if r.user != request.user:
|
try:
|
||||||
messages.error(
|
_ = st_integration.open()
|
||||||
request, 'You can only access your own workouts on the NK Logbook, not those of your athletes')
|
except NoTokenError:
|
||||||
url = reverse('workout_sporttracksimport_view',
|
|
||||||
kwargs={'userid': request.user.id})
|
|
||||||
return HttpResponseRedirect(url)
|
|
||||||
|
|
||||||
res = sporttracksstuff.get_sporttracks_workout_list(request.user)
|
|
||||||
if (res.status_code != 200):
|
|
||||||
if (res.status_code == 401):
|
|
||||||
r = getrower(request.user)
|
|
||||||
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
|
|
||||||
s = "Token doesn't exist. Need to authorize"
|
|
||||||
return HttpResponseRedirect("/rowers/me/sporttracksauthorize/")
|
return HttpResponseRedirect("/rowers/me/sporttracksauthorize/")
|
||||||
else: # pragma: no cover
|
|
||||||
return HttpResponseRedirect("/rowers/me/sporttracksrefresh/")
|
workouts = st_integration.get_workout_list()
|
||||||
message = "Something went wrong in workout_sporttracksimport_view" # pragma: no cover
|
|
||||||
messages.error(request, message) # pragma: no cover
|
r = getrower(request.user)
|
||||||
if settings.DEBUG: # pragma: no cover
|
|
||||||
return HttpResponse(res)
|
if request.method == "POST":
|
||||||
else: # pragma: no cover
|
try: # pragma: no cover
|
||||||
|
tdict = dict(request.POST.lists())
|
||||||
|
ids = tdict['workoutid']
|
||||||
|
stids = [int(id) for id in ids]
|
||||||
|
alldata = {}
|
||||||
|
|
||||||
|
for id in stids:
|
||||||
|
res = st_integration.get_workout(id)
|
||||||
|
|
||||||
|
# done, redirect to workouts list
|
||||||
|
messages.info(request,
|
||||||
|
'Your SportTracks workouts will be imported in the background.'
|
||||||
|
' It may take a few minutes before they appear.')
|
||||||
url = reverse('workouts_view')
|
url = reverse('workouts_view')
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
except KeyError: # pragma: no cover
|
||||||
workouts = []
|
pass
|
||||||
|
|
||||||
knownstids = uniqify([
|
|
||||||
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
|
|
||||||
])
|
|
||||||
for item in res.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', 'type', 'name', 'new']
|
|
||||||
values = [i, d, ttot, s, r, n, nnn]
|
|
||||||
res = dict(zip(keys, values))
|
|
||||||
workouts.append(res)
|
|
||||||
|
|
||||||
r = getrower(request.user)
|
|
||||||
|
|
||||||
breadcrumbs = [
|
breadcrumbs = [
|
||||||
{
|
{
|
||||||
@@ -1476,12 +1411,17 @@ def workout_sporttracksimport_view(request, message="", userid=0):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return render(request, 'sporttracks_list_import.html',
|
checknew = request.GET.get('selectallnew', False)
|
||||||
|
|
||||||
|
|
||||||
|
return render(request, 'list_import.html',
|
||||||
{'workouts': workouts,
|
{'workouts': workouts,
|
||||||
'breadcrumbs': breadcrumbs,
|
'breadcrumbs': breadcrumbs,
|
||||||
'active': 'nav-workouts',
|
'active': 'nav-workouts',
|
||||||
'rower': r,
|
'rower': r,
|
||||||
'teams': get_my_teams(request.user),
|
'teams': get_my_teams(request.user),
|
||||||
|
'integration':'SportTracks',
|
||||||
|
'checknew': checknew,
|
||||||
})
|
})
|
||||||
|
|
||||||
return HttpResponse(res) # pragma: no cover
|
return HttpResponse(res) # pragma: no cover
|
||||||
@@ -1630,7 +1570,7 @@ importsources = {
|
|||||||
'strava': StravaIntegration,
|
'strava': StravaIntegration,
|
||||||
'polar': polarstuff,
|
'polar': polarstuff,
|
||||||
'ownapi': ownapistuff,
|
'ownapi': ownapistuff,
|
||||||
'sporttracks': sporttracksstuff,
|
'sporttracks': SportTracksIntegration,
|
||||||
'trainingpeaks': tpstuff,
|
'trainingpeaks': tpstuff,
|
||||||
'nk': NKIntegration,
|
'nk': NKIntegration,
|
||||||
}
|
}
|
||||||
@@ -1698,27 +1638,9 @@ def workout_getimportview_old(request, externalid, source='c2', do_async=True):
|
|||||||
# Imports all new workouts from SportTracks
|
# Imports all new workouts from SportTracks
|
||||||
@login_required()
|
@login_required()
|
||||||
def workout_getsporttracksworkout_all(request):
|
def workout_getsporttracksworkout_all(request):
|
||||||
res = sporttracksstuff.get_sporttracks_workout_list(request.user)
|
st_integration = SportTracksIntegration(request.user)
|
||||||
if (res.status_code == 200):
|
st_integration.get_workouts()
|
||||||
r = getrower(request.user)
|
messages.info(request,"Your SportTracks workouts will be imported in the background")
|
||||||
stids = [int(getidfromuri(item['uri']))
|
|
||||||
for item in res.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 = sporttracksstuff.get_workout(
|
|
||||||
request.user, sporttracksid)
|
|
||||||
|
|
||||||
if id == 0: # pragma: no cover
|
|
||||||
messages.error(
|
|
||||||
request, "Something went wrong with workout {id}".format(id=sporttracksid))
|
|
||||||
|
|
||||||
else:
|
|
||||||
w = Workout.objects.get(id=id)
|
|
||||||
w.uploadedtosporttracks = sporttracksid
|
|
||||||
w.save()
|
|
||||||
|
|
||||||
url = reverse('workouts_view')
|
url = reverse('workouts_view')
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
@@ -1761,27 +1683,9 @@ def workout_getimportview(request, externalid, source='c2', do_async=True):
|
|||||||
# Imports all new workouts from SportTracks
|
# Imports all new workouts from SportTracks
|
||||||
@login_required()
|
@login_required()
|
||||||
def workout_getsporttracksworkout_all(request):
|
def workout_getsporttracksworkout_all(request):
|
||||||
res = sporttracksstuff.get_sporttracks_workout_list(request.user)
|
st_integration = SportTracksIntegration(request.user)
|
||||||
if (res.status_code == 200):
|
_ = st_integration.get_workouts()
|
||||||
r = getrower(request.user)
|
messages.info(request,"Your SportTracks workouts will be imported in the background")
|
||||||
stids = [int(getidfromuri(item['uri']))
|
|
||||||
for item in res.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 = sporttracksstuff.get_workout(
|
|
||||||
request.user, sporttracksid)
|
|
||||||
|
|
||||||
if id == 0: # pragma: no cover
|
|
||||||
messages.error(
|
|
||||||
request, "Something went wrong with workout {id}".format(id=sporttracksid))
|
|
||||||
|
|
||||||
else:
|
|
||||||
w = Workout.objects.get(id=id)
|
|
||||||
w.uploadedtosporttracks = sporttracksid
|
|
||||||
w.save()
|
|
||||||
|
|
||||||
url = reverse('workouts_view')
|
url = reverse('workouts_view')
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ import datetime
|
|||||||
import iso8601
|
import iso8601
|
||||||
import rowers.rojabo_stuff as rojabo_stuff
|
import rowers.rojabo_stuff as rojabo_stuff
|
||||||
from rowers.rp3stuff import rp3_open
|
from rowers.rp3stuff import rp3_open
|
||||||
from rowers.sporttracksstuff import sporttracks_open
|
|
||||||
from rowers.tpstuff import tp_open
|
from rowers.tpstuff import tp_open
|
||||||
from iso8601 import ParseError
|
from iso8601 import ParseError
|
||||||
|
|
||||||
@@ -203,7 +202,6 @@ import rowers.garmin_stuff as garmin_stuff
|
|||||||
|
|
||||||
from rowers.rojabo_stuff import rojabo_open
|
from rowers.rojabo_stuff import rojabo_open
|
||||||
import rowers.polarstuff as polarstuff
|
import rowers.polarstuff as polarstuff
|
||||||
import rowers.sporttracksstuff as sporttracksstuff
|
|
||||||
|
|
||||||
from rowers.integrations import *
|
from rowers.integrations import *
|
||||||
|
|
||||||
|
|||||||
@@ -5654,10 +5654,9 @@ def workout_upload_view(request,
|
|||||||
messages.error(request, message)
|
messages.error(request, message)
|
||||||
|
|
||||||
if (upload_to_st): # pragma: no cover
|
if (upload_to_st): # pragma: no cover
|
||||||
|
st_integration = SportTracksIntegration(request.user)
|
||||||
try:
|
try:
|
||||||
message, id = sporttracksstuff.workout_sporttracks_upload(
|
id = st_integration.workout_export(w)
|
||||||
request.user, w
|
|
||||||
)
|
|
||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
message = "Please connect to SportTracks first"
|
message = "Please connect to SportTracks first"
|
||||||
id = 0
|
id = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user