Private
Public Access
1
0

runkeeper retired, updated sporttraclks

This commit is contained in:
Sander Roosendaal
2021-05-21 11:30:57 +02:00
parent b58ba932fd
commit b53b56f909
21 changed files with 74 additions and 906 deletions

View File

@@ -46,7 +46,6 @@ class RowerInline(admin.StackedInline):
'sporttrackstoken','sporttrackstokenexpirydate',
'sporttracksrefreshtoken',
'sporttracks_auto_export',
'mapmyfitness_auto_export',
'tptoken','tptokenexpirydate','tprefreshtoken',
'trainingpeaks_auto_export',
'polartoken','polartokenexpirydate',
@@ -55,7 +54,6 @@ class RowerInline(admin.StackedInline):
'stravatoken','stravatokenexpirydate','stravarefreshtoken',
'stravaexportas','strava_auto_export',
'strava_auto_import',
'runkeepertoken','runkeeper_auto_export',
'garmintoken','garminrefreshtoken')}),
('Team',
{'fields':('friends','privacy','team')}),

View File

@@ -825,14 +825,9 @@ def make_authorization_url(request): # pragma: no cover
return HttpResponseRedirect(url)
# Get workout from C2 ID
def get_workout(user,c2id,do_async=False):
def get_workout(user,c2id,do_async=True):
r = Rower.objects.get(user=user)
if (r.c2token == '') or (r.c2token is None): # pragma: no cover
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s) ,0
elif (timezone.now()>r.tokenexpirydate):
s = "Token expired. Needs to refresh."
return custom_exception_handler(401,s),0
thetoken = c2_open(user)
job = myqueue(queuehigh,
handle_c2_getworkout,

View File

@@ -988,7 +988,6 @@ class Rower(models.Model):
sporttracksrefreshtoken = models.CharField(default='',max_length=200,
blank=True,null=True)
sporttracks_auto_export = models.BooleanField(default=False)
mapmyfitness_auto_export = models.BooleanField(default=False)
tptoken = models.CharField(default='',max_length=1000,blank=True,null=True)
tptokenexpirydate = models.DateTimeField(blank=True,null=True)
tprefreshtoken = models.CharField(default='',max_length=1000,
@@ -1037,9 +1036,6 @@ class Rower(models.Model):
strava_auto_export = models.BooleanField(default=False)
strava_auto_import = models.BooleanField(default=False)
strava_auto_delete = models.BooleanField(default=False)
runkeepertoken = models.CharField(default='',max_length=200,
blank=True,null=True)
runkeeper_auto_export = models.BooleanField(default=False)
privacychoices = (
@@ -3198,7 +3194,6 @@ class Workout(models.Model):
uploadedtostrava = models.BigIntegerField(default=0)
uploadedtosporttracks = models.BigIntegerField(default=0)
uploadedtotp = models.BigIntegerField(default=0)
uploadedtorunkeeper = models.BigIntegerField(default=0)
uploadedtogarmin = models.BigIntegerField(default=0)
uploadedtorp3 = models.BigIntegerField(default=0)
uploadedtonk = models.BigIntegerField(default=0)
@@ -3279,7 +3274,6 @@ class TombStone(models.Model):
uploadedtostrava = models.BigIntegerField(default=0)
uploadedtosporttracks = 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)
@@ -3289,7 +3283,6 @@ def create_tombstone_on_delete(sender, instance, **kwargs):
uploadedtoc2 = instance.uploadedtoc2,
uploadedtostrava = instance.uploadedtostrava,
uploadedtotp = instance.uploadedtotp,
uploadedtorunkeeper = instance.uploadedtorunkeeper,
uploadedtonk = instance.uploadedtonk
)
t.save()
@@ -3864,8 +3857,6 @@ class RowerExportForm(ModelForm):
'c2_auto_export',
'c2_auto_import',
'nk_auto_import',
'mapmyfitness_auto_export',
'runkeeper_auto_export',
'sporttracks_auto_export',
'strava_auto_export',
'strava_auto_import',
@@ -4208,8 +4199,6 @@ class RowerImportExportForm(ModelForm):
'polar_auto_import',
'c2_auto_export',
'c2_auto_import',
'mapmyfitness_auto_export',
'runkeeper_auto_export',
'sporttracks_auto_export',
'strava_auto_export',
'strava_auto_import',

View File

@@ -1,530 +0,0 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
# All the functionality needed to connect to Runkeeper
from rowers.imports import *
import re
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
from rowers.utils import myqueue
from rowers.rower_rules import is_workout_user
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET,RUNKEEPER_REDIRECT_URI,
)
from rowers.tasks import handle_runkeeper_sync
oauth_data = {
'client_id': RUNKEEPER_CLIENT_ID,
'client_secret': RUNKEEPER_CLIENT_SECRET,
'redirect_uri': RUNKEEPER_REDIRECT_URI,
'autorization_uri': "https://www.runkeeper.com/apps/authorize",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'runkeepertoken',
'refreshtokenname': None,
'expirydatename': None,
'bearer_auth': True,
'base_url': "https://runkeeper.com/apps/token",
'headers': {'user-agent': 'sanderroosendaal'},
'scope':'write',
}
import numpy
def splitrunkeeperlatlongdata(lijst,tname,latname,lonname):
t = []
lat = []
lon = []
for d in lijst:
t.append(d[tname])
lat.append(d[latname])
lon.append(d[lonname])
return [np.array(t),np.array(lat),np.array(lon)]
def splitrunkeeperdata(lijst,xname,yname):
x = []
y = []
for d in lijst:
x.append(d[xname])
y.append(d[yname])
return [np.array(x),np.array(y)]
# Checks if user has SportTracks token, renews them if they are expired
def runkeeper_open(user):
return imports_open(user,oauth_data)
# Exchange access code for long-lived access token
def get_token(code):
return imports_get_token(code,oauth_data)
# Make authorization URL including random string
def make_authorization_url(request): # pragma: no cover
return imports_make_authorization_url(oauth_data)
# Get list of workouts available on Runkeeper
def get_runkeeper_workout_list(user):
r = Rower.objects.get(user=user)
if (r.runkeepertoken == '') or (r.runkeepertoken is None):
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s)
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.runkeepertoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.runkeeper.com/fitnessActivities"
s = requests.get(url,headers=headers)
return s
# Get workout summary data by Runkeeper ID
def get_workout(user,runkeeperid,do_async=False):
r = Rower.objects.get(user=user)
if (r.runkeepertoken == '') or (r.runkeepertoken is None): # pragma: no cover
return custom_exception_handler(401,s)
s = "Token doesn't exist. Need to authorize"
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.runkeepertoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.runkeeper.com/fitnessActivities/"+str(runkeeperid)
s = requests.get(url,headers=headers)
try:
data = s.json()
except ValueError: # pragma: no cover
data = {}
return data,"Something went wrong with the workout import"
strokedata = pd.DataFrame.from_dict({
key: pd.Series(value) for key, value in data.items()
})
return data,strokedata
# Create Workout Data for upload to SportTracks
def createrunkeeperworkoutdata(w):
filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except: # pragma: no cover
return 0
try:
averagehr = int(row.df[' HRCur (bpm)'].mean())
maxhr = int(row.df[' HRCur (bpm)'].max())
except KeyError: # pragma: no cover
averagehr = 0
maxhr = 0
duration = w.duration.hour*3600
duration += w.duration.minute*60
duration += w.duration.second
duration += +1.0e-6*w.duration.microsecond
try:
t = row.df.loc[:,'TimeStamp (sec)'].values-row.df.loc[:,'TimeStamp (sec)'].iloc[0]
except KeyError: # pragma: no cover
return pd.DataFrame()
t[0] = t[1]
d = row.df.loc[:,'cum_dist'].values
d[0] = d[1]
t = t.astype(int)
d = d.astype(int)
spm = row.df[' Cadence (stokes/min)'].astype(int)
spm[0] = spm[1]
hr = row.df[' HRCur (bpm)'].astype(int)
haslatlon=1
try:
lat = row.df[' latitude'].values
lon = row.df[' longitude'].values
if not lat.std() and not lon.std(): # pragma: no cover
haslatlon = 0
except KeyError: # pragma: no cover
haslatlon = 0
t = t.tolist()
hr = hr.tolist()
d = d.tolist()
# path data
if haslatlon:
lat = lat.tolist()
lon = lon.tolist()
locdata = []
for e in zip(t,lat,lon):
point = {'timestamp':e[0],
'latitude':e[1],
'longitude':e[2],
'altitude':0,
"type":"gps"}
locdata.append(point)
hrdata = []
for e in zip(t,hr):
point = {'timestamp':e[0],
'heart_rate':e[1]
}
hrdata.append(point)
distancedata = []
for e in zip(t,d):
point = {'timestamp':e[0],
'distance':e[1]
}
distancedata.append(point)
st = w.startdatetime.astimezone(pytz.timezone(w.timezone))
start_time = st.strftime("%a, %d %b %Y %H:%M:%S")
try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
if haslatlon:
data = {
"type": "Rowing",
"start_time": start_time,
"total_distance": int(w.distance),
"duration": duration,
"notes": newnotes,
"average_heart_rate": averagehr,
"path": locdata,
"distance": distancedata,
"heart_rate": hrdata,
"post_to_twitter":"false",
"post_to_facebook":"false",
}
else:
data = {
"type": "Rowing",
"start_time": start_time,
"total_distance": int(w.distance),
"duration": duration,
"notes": newnotes,
"avg_heartrate": averagehr,
"distance": distancedata,
"heart_rate": hrdata,
"post_to_twitter":"false",
"post_to_facebook":"false",
}
return data
# Obtain Runkeeper Workout ID from the response returned on successful
# upload
def getidfromresponse(response):
uri = response.headers["Location"]
tester = re.compile('^\/fitnessActivities\/(\d+)$')
id = int(tester.match(uri).group(1))
return int(id)
def geturifromid(access_token,id): # pragma: no cover
authorizationstring = str('Bearer ' + access_token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "https://api.runkeeper.com/fitnessActivities/"+str(id)
response = requests.get(url,headers=headers)
try:
me_json = response.json()
except:
return ''
try:
res = me_json['uri']
except KeyError:
res = ''
return res
# Get user id, having access token
# Handy for checking if the API access is working
def get_userid(access_token):
authorizationstring = str('Bearer ' + access_token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "https://api.runkeeper.com/user"
response = requests.get(url,headers=headers)
try:
me_json = response.json()
except: # pragma: no cover
return ''
try:
res = me_json['userID']
except KeyError: # pragma: no cover
res = ''
return str(res)
def default(o): # pragma: no cover
if isinstance(o, numpy.int64): return int(o)
raise TypeError
def workout_runkeeper_upload(user,w,asynchron=False): # pragma: no cover
message = "Uploading to Runkeeper"
rkid = 0
r = w.user
thetoken = runkeeper_open(r.user)
# ready to upload. Hurray
if (is_workout_user(user,w)):
data = createrunkeeperworkoutdata(w)
if not data:
message = "Data error in Runkeeper Upload"
rkid = 0
return message, rkid
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json',
'Content-Length':'nnn'}
url = "https://api.runkeeper.com/fitnessActivities"
if asynchron:
job = myqueue(queue,handle_runkeeper_sync,
w.id,url,headers,json.dumps(data,default=default))
return "Asynchronous sync",0
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
# check for duplicate error first
if (response.status_code == 409 ):
message = "Duplicate error"
w.uploadedtorunkeeper = -1
rkid = -1
w.save()
return message, rkid
elif (response.status_code == 201 or response.status_code==200):
rkid = getidfromresponse(response)
rkuri = geturifromid(thetoken,rkid)
w.uploadedtorunkeeper = rkid
w.save()
return 'Successfully synchronized to Runkeeper',rkid
else:
s = response
message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text)
rkid = 0
return message, rkid
else:
message = "You are not authorized to upload this workout"
rkid = 0
return message, rkid
return message,rkid
# Create workout from RunKeeper Data
def add_workout_from_data(user,importid,data,strokedata,source='runkeeper',
workoutsource='runkeeper'):
# To Do - add utcoffset to time
workouttype = data['type']
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'other'
try:
comments = data['notes']
except: # pragma: no cover
comments = ''
try:
utcoffset = tz(data['utcoffset'])
except:
utcoffset = 0
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['start_time'])
except iso8601.ParseError:
try: # pragma: no cover
rowdatetime = datetime.strptime(data['start_time'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except ValueError:
try:
rowdatetime = parser.parse(data['start_time'])
#rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except: # pragma: no cover
rowdatetime = datetime.strptime(data['date'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
starttimeunix = arrow.get(rowdatetime).timestamp()
#starttimeunix = mktime(rowdatetime.utctimetuple())
starttimeunix += utcoffset*3600
try:
title = data['name']
except:
title = "Imported data"
res = splitrunkeeperdata(data['distance'],'timestamp','distance')
distance = res[1]
times_distance = res[0]
try:
l = data['path']
res = splitrunkeeperlatlongdata(l,'timestamp','latitude','longitude')
times_location = res[0]
latcoord = res[1]
loncoord = res[2]
except: # pragma: no cover
times_location = times_distance
latcoord = np.zeros(len(times_distance))
loncoord = np.zeros(len(times_distance))
if workouttype in types.otwtypes:
workouttype = 'rower'
try: # pragma: no cover
res = splitrunkeeperdata(data['cadence'],'timestamp','cadence')
times_spm = res[0]
spm = res[1]
except KeyError:
times_spm = times_distance
spm = 0*times_distance
try:
res = splitrunkeeperdata(data['heart_rate'],'timestamp','heart_rate')
hr = res[1]
times_hr = res[0]
except KeyError: # pragma: no cover
times_hr = times_distance
hr = 0*times_distance
# create data series and remove duplicates
distseries = pd.Series(distance,index=times_distance)
distseries = distseries.groupby(distseries.index).first()
latseries = pd.Series(latcoord,index=times_location)
try:
latseries = latseries.groupby(latseries.index).first()
except TypeError: # pragma: no cover
latseries = 0.0*distseries
lonseries = pd.Series(loncoord,index=times_location)
try:
lonseries = lonseries.groupby(lonseries.index).first()
except TypeError: # pragma: no cover
lonseries = 0.0*distseries
spmseries = pd.Series(spm,index=times_spm)
spmseries = spmseries.groupby(spmseries.index).first()
hrseries = pd.Series(hr,index=times_hr)
try:
hrseries = hrseries.groupby(hrseries.index).first()
except TypeError: # pragma: no cover
hrseries = 0*distseries
# Create dicts and big dataframe
d = {
' Horizontal (meters)': distseries,
' latitude': latseries,
' longitude': lonseries,
' Cadence (stokes/min)': spmseries,
' HRCur (bpm)' : hrseries,
}
df = pd.DataFrame(d)
df = df.groupby(level=0).last()
cum_time = df.index.values
df[' ElapsedTime (sec)'] = cum_time
velo = df[' Horizontal (meters)'].diff()/df[' ElapsedTime (sec)'].diff()
df[' Power (watts)'] = 0.0*velo
nr_rows = len(velo.values)
df[' DriveLength (meters)'] = np.zeros(nr_rows)
df[' StrokeDistance (meters)'] = np.zeros(nr_rows)
df[' DriveTime (ms)'] = np.zeros(nr_rows)
df[' StrokeRecoveryTime (ms)'] = np.zeros(nr_rows)
df[' AverageDriveForce (lbs)'] = np.zeros(nr_rows)
df[' PeakDriveForce (lbs)'] = np.zeros(nr_rows)
df[' lapIdx'] = np.zeros(nr_rows)
unixtime = cum_time+starttimeunix
try:
unixtime[0] = starttimeunix
except IndexError: # pragma: no cover
return (0,'No data to import')
df['TimeStamp (sec)'] = unixtime
dt = np.diff(cum_time).mean()
wsize = round(5./dt)
# velo2 = stravastuff.ewmovingaverage(velo,wsize)
# df[' Stroke500mPace (sec/500m)'] = 500./velo2
df = df.fillna(0)
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
# csvfilename ='media/Import_'+str(importid)+'.csv'
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
id,message = dataprep.save_workout_database(csvfilename,r,
workouttype=workouttype,
workoutsource='runkeeper',
title=title,
dosmooth=r.dosmooth,
notes=comments)
return (id,message)

View File

@@ -120,7 +120,14 @@ def get_workout(user,sporttracksid,do_async=False):
key: pd.Series(value) for key, value in data.items()
})
return data,strokedata
id,message = add_workout_from_data(
user,
sporttracksid,data,
strokedata,
source='sporttracks',
workoutsource='sporttracks')
return id
# Create Workout Data for upload to SportTracks
def createsporttracksworkoutdata(w):

View File

@@ -25,7 +25,6 @@ from rowingdata import rowingdata as rdata
from datetime import timedelta
from sqlalchemy import create_engine
from rowers.imports import splituadata
#from celery import app
from rowers.celery import app

View File

@@ -192,21 +192,6 @@
</a>
{% endif %}
</li>
<li id="export-rk">
{% if workout.uploadedtorunkeeper %}
<a href="https://runkeeper.com/user/{{ user|rkuserid }}/activity/{{ workout.uploadedtorunkeeper }}">
Runkeeper <i class="fas fa-check"></i>
</a>
{% elif user.rower.runkeepertoken == None or user.rower.runkeepertoken == '' %}
<a href="/rowers/me/runkeeperauthorize">
Connect to Runkeeper
</a>
{% else %}
<a href="/rowers/workout/{{ workout.id|encode }}/runkeeperuploadw/">
Runkeeper
</a>
{% endif %}
</li>
<li id="export-tp">
{% if workout.uploadedtotp %}
<a href="https://app.trainingpeaks.com">

View File

@@ -40,7 +40,6 @@
<li id="concept2"><a href="/rowers/workout/c2list/">Concept2</a></li>
<li id="nklink"><a href="/rowers/workout/nkimport/">NK Logbook</a></li>
<li id="strava"><a href="/rowers/workout/stravaimport/">Strava</a></li>
<li id="runkeeper"><a href="/rowers/workout/runkeeperimport/">RunKeeper</a></li>
<li id="sporttracks"><a href="/rowers/workout/sporttracksimport/">SportTracks</a></li>
<li id="polar"><a href="/rowers/workout/polarimport/">Polar</a></li>
<li id="rp3"><a href="/rowers/workout/rp3import/">RP3</a></li>

View File

@@ -29,9 +29,6 @@
{% if rower.stravatoken is not None and rower.stravatoken != '' %}
Strava,
{% endif %}
{% if rower.runkeepertoken is not None and rower.runkeepertoken != '' %}
Runkeeper,
{% endif %}
{% if rower.rp3token is not None and rower.rp3token != '' %}
RP3
{% endif %}
@@ -76,7 +73,6 @@
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
<p><a href="/rowers/me/nkauthorize/"><img src="/static/img/NKLiNKLogbook.png" alt="connect with NK Logbook" width="120"></a></p>
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
<p><a href="/rowers/me/polarauthorize/"><img src="/static/img/Polar_connectwith_btn_white.png"
alt="connect with Polar" width="130"></a></p>
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png"

View File

@@ -19,9 +19,8 @@ from rowers.plannedsessions import (
race_can_register, race_can_submit,race_rower_status
)
from rowers import c2stuff, runkeeperstuff
from rowers import c2stuff
from rowers.c2stuff import c2_open
from rowers.runkeeperstuff import runkeeper_open
from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispromember
from rowers.mytypes import (
otwtypes,adaptivetypes,sexcategories,weightcategories,workouttypes,
@@ -453,16 +452,6 @@ def currency(word):
return '{amount:.2f}'.format(amount=amount)
@register.filter
def rkuserid(user):
try:
thetoken = runkeeper_open(user)
except NoTokenError: # pragma: no cover
return 0
rkuserid = runkeeperstuff.get_userid(thetoken)
return rkuserid
@register.filter
def courselength(course): # pragma: no cover

View File

@@ -62,7 +62,6 @@ from mock import Mock, patch
import pandas as pd
import rowers.c2stuff as c2stuff
import rowers.sporttracksstuff as sporttracksstuff
import rowers.runkeeperstuff as runkeeperstuff
from django.urls import reverse, reverse_lazy

View File

@@ -313,8 +313,10 @@ class C2Objects(DjangoTestCase):
response = self.c.get('/rowers/workout/c2import/12/',follow=True)
expected_url = reverse('workout_c2import_view')
self.assertRedirects(response,
expected_url='/rowers/workout/'+encoded2+'/edit/',
expected_url=expected_url,
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
@@ -485,13 +487,10 @@ class C2ObjectsTokenExpired(DjangoTestCase):
@patch('rowers.dataprep.create_engine')
def test_c2_import(self, mock_get, mocked_sqlalchemy):
response = self.c.get('/rowers/workout/c2import/12/',follow=True)
response = self.c.get('/rowers/workout/c2import/12/')
self.assertRedirects(response,
expected_url='/rowers/list-workouts/',
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 302)
#@pytest.mark.django_db
@override_settings(TESTING=True)
@@ -605,8 +604,10 @@ class NKObjects(DjangoTestCase):
result = rowers.nkstuff.rower_nk_token_refresh(self.u)
response = self.c.get('/rowers/workout/nkimport/469',follow=True)
expected_url = reverse('workout_nkimport_view')
self.assertRedirects(response,
expected_url='/rowers/workout/'+encoded1+'/edit/',
expected_url=expected_url,
status_code=301,target_status_code=200)
self.assertEqual(response.status_code, 200)
@@ -626,8 +627,10 @@ class NKObjects(DjangoTestCase):
result = rowers.nkstuff.rower_nk_token_refresh(self.u)
response = self.c.get('/rowers/workout/nkimport/404',follow=True)
expected_url = reverse('workout_nkimport_view')
self.assertRedirects(response,
expected_url='/rowers/workout/'+encoded1+'/edit/',
expected_url=expected_url,
status_code=301,target_status_code=200)
self.assertEqual(response.status_code, 200)
@@ -889,9 +892,10 @@ class StravaObjects(DjangoTestCase):
mocked_getsmallrowdata_db):
response = self.c.get('/rowers/workout/stravaimport/12',follow=True)
expected_url = reverse('workout_stravaimport_view')
self.assertRedirects(response,
expected_url='/rowers/workout/'+encoded2+'/edit/',
expected_url=expected_url,
status_code=301,target_status_code=200)
self.assertEqual(response.status_code, 200)
@@ -1014,8 +1018,7 @@ class STObjects(DjangoTestCase):
response = self.c.get('/rowers/workout/sporttracksimport/13/',follow=True)
expected_url = reverse('workout_edit_view',
kwargs = {'id':encoder.encode_hex(2)})
expected_url = '/rowers/workout/sporttracksimport/'
self.assertRedirects(response,
expected_url=expected_url,
@@ -1056,97 +1059,6 @@ class STObjects(DjangoTestCase):
res = add_workout_from_data(self.u,1,data,data)
#@pytest.mark.django_db
@override_settings(TESTING=True)
class RunKeeperObjects(DjangoTestCase):
def setUp(self):
self.c = Client()
self.u = User.objects.create_user('john',
'sander@ds.ds',
'koeinsloot')
self.u.first_name = 'John'
self.u.last_name = 'Sander'
self.u.save()
self.r = Rower.objects.create(user=self.u,gdproptin=True,surveydone=True,
gdproptindate=timezone.now()
)
self.r.runkeepertoken = '12'
self.r.save()
self.c.login(username='john',password='koeinsloot')
self.nu = datetime.datetime.now()
filename = 'rowers/tests/testdata/testdata.csv'
rr = rrower(hrmax=self.r.max,hrut2=self.r.ut2,
hrut1=self.r.ut1,hrat=self.r.at,
hrtr=self.r.tr,hran=self.r.an,ftp=self.r.ftp)
row = rdata(csvfile=filename,rower=rr)
totaldist = row.df['cum_dist'].max()
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
totaltime = totaltime+row.df.loc[:,' ElapsedTime (sec)'].iloc[0]
hours = int(totaltime/3600.)
minutes = int((totaltime - 3600.*hours)/60.)
seconds = int(totaltime - 3600.*hours - 60.*minutes)
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
self.w = Workout.objects.create(
name='testworkout',workouttype='water',
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
starttime=workoutstarttime,
startdatetime=row.rowdatetime,
duration=duration,distance=totaldist,
csvfilename=filename
)
@patch('rowers.runkeeperstuff.requests.post', side_effect=mocked_requests)
def test_runkeeper_callback(self, mock_post):
response = self.c.get('/runkeeper_callback?code=dsdoij232s',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.runkeeperstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.runkeeperstuff.requests.get', side_effect=mocked_requests)
def test_runkeeper_upload(self, mock_get, mock_post):
response = self.c.get('/rowers/workout/'+encoded1+'/runkeeperuploadw/')
self.assertRedirects(response,
expected_url = '/rowers/workout/'+encoded1+'/edit/',
status_code=302,target_status_code=200)
self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/')
self.assertEqual(response.status_code, 302)
@patch('rowers.runkeeperstuff.requests.get', side_effect=mocked_requests)
def test_runkeeper_list(self, mock_get):
response = self.c.get('/rowers/workout/runkeeperimport',follow=True)
self.assertEqual(response.status_code,200)
@patch('rowers.imports.requests.get', side_effect=mocked_requests)
def test_runkeeper_import(self, mock_get):
response = self.c.get('/rowers/workout/runkeeperimport/12/',follow=True)
self.assertRedirects(response,
expected_url='/rowers/workout/'+encoded2+'/edit/',
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
#@pytest.mark.django_db

View File

@@ -99,7 +99,6 @@ class TraverseLinksTest(TestCase):
'.*authorize.*',
'.*youtu.*',
'.*earth.*',
'.*runkeeper.*',
'.*c2list.*',
'.*stravaimport.*',
'.*performancephones.*',

View File

@@ -124,7 +124,6 @@ def matchsync(line):
tester3 = tester+'(.*)((tp)|(trainingpeaks))'
tester4 = tester+'(.*)(strava)'
tester5 = tester+'(.*)((st)|(sporttracks))'
tester6 = tester+'(.*)((rk)|(runkeeper))'
tester = re.compile(tester)
@@ -134,7 +133,6 @@ def matchsync(line):
('upload_totp',re.compile(tester3)),
('upload_to_Strava',re.compile(tester4)),
('upload_to_SportTracks',re.compile(tester5)),
('upload_to_RunKeeper',re.compile(tester6)),
('upload_to_MapMyFitness',re.compile(tester7)),
]
for t in testers:
@@ -259,8 +257,6 @@ def getsyncoptions(uploadoptions,values): # pragma: no cover
uploadoptions['upload_to_Strava'] = True
if v in ['st','sporttracks']:
uploadoptions['upload_to_SportTracks'] = True
if v in ['rk','runkeeper']:
uploadoptions['upload_to_RunKeeper'] = True
except AttributeError:
pass
@@ -487,7 +483,6 @@ def make_plot(r,w,f1,f2,plottype,title,imagename='',plotnr=0):
import rowers.c2stuff as c2stuff
import rowers.stravastuff as stravastuff
import rowers.sporttracksstuff as sporttracksstuff
import rowers.runkeeperstuff as runkeeperstuff
import rowers.tpstuff as tpstuff
@@ -641,16 +636,6 @@ def do_sync(w,options, quick=False):
id = 0
if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export): # pragma: no cover
try:
message,id = runkeeperstuff.workout_runkeeper_upload(
w.user.user,w,asynchron=True,
)
except NoTokenError:
message = "Please connect to Runkeeper first"
id = 0
if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export): # pragma: no cover
try:

View File

@@ -508,13 +508,10 @@ urlpatterns = [
re_path(r'^workout/sporttracksimport/all/$',views.workout_getsporttracksworkout_all,name='workout_getsporttracksworkout_all'),
re_path(r'^workout/polarimport/$',views.workout_polarimport_view,name='workout_polarimport_view'),
re_path(r'^workout/polarimport/user/(?P<userid>\d+)/',views.workout_polarimport_view,name='workout_polarimport_view'),
re_path(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'),
re_path(r'^workout/runkeeperimport/user/(?P<userid>\d+)/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/c2uploadw/$',views.workout_c2_upload_view,name='workout_c2_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/stravauploadw/$',views.workout_strava_upload_view,name='workout_strava_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/recalcsummary/$',views.workout_recalcsummary_view,name='workout_recalcsummary_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/sporttracksuploadw/$',views.workout_sporttracks_upload_view,name='workout_sporttracks_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/runkeeperuploadw/$',views.workout_runkeeper_upload_view,name='workout_runkeeper_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/tpuploadw/$',views.workout_tp_upload_view,name='workout_tp_upload_view'),
re_path(r'^multi-compare/workout/(?P<id>\b[0-9A-Fa-f]+\b)/user/(?P<userid>\d+)/$',views.multi_compare_view,
name='multi_compare_view'),
@@ -599,7 +596,6 @@ urlpatterns = [
re_path(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize,name='rower_sporttracks_authorize'),
re_path(r'^me/tpauthorize/$',views.rower_tp_authorize,name='rower_tp_authorize'),
re_path(r'^me/rp3authorize/$',views.rower_rp3_authorize,name='rower_rp3_authorize'),
re_path(r'^me/runkeeperauthorize/$',views.rower_runkeeper_authorize,name='rower_runkeeper_authorize'),
re_path(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh,name='rower_sporttracks_token_refresh'),
re_path(r'^me/tprefresh/$',views.rower_tp_token_refresh,name='rower_tp_token_refresh'),
re_path(r'^me/c2refresh/$',views.rower_c2_token_refresh,name='rower_c2_token_refresh'),

View File

@@ -15,7 +15,7 @@ import uuid
import datetime
import json
import time
from fitparse import FitFile
from django.conf import settings

View File

@@ -225,67 +225,6 @@ def workout_c2_upload_view(request,id=0):
return response
# Upload workout to RunKeeper
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_runkeeper_upload_view(request,id=0):
message = ""
w = get_workout(id)
r = w.user
try:
thetoken = runkeeper_open(r.user)
except NoTokenError: # pragma: no cover
return HttpResponseRedirect("/rowers/me/runkeeperauthorize/")
# ready to upload. Hurray
data = runkeeperstuff.createrunkeeperworkoutdata(w)
if not data: # pragma: no cover
message = "Data error"
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
})
return HttpResponseRedirect(url)
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json',
'Content-Length':'nnn'}
url = "https://api.runkeeper.com/fitnessActivities"
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
# check for duplicate error first
if (response.status_code == 409 ): # pragma: no cover # pragma: no cover
message = "Duplicate error"
messages.error(request,message)
w.uploadedtorunkeeper = -1
w.save()
elif (response.status_code == 201 or response.status_code==200):
runkeeperid = runkeeperstuff.getidfromresponse(response)
w.uploadedtorunkeeper = runkeeperid
w.save()
url = reverse('workout_edit_view',
kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url)
else: # pragma: no cover
s = response
message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text)
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
}) # pragma: no cover
return HttpResponseRedirect(url) # pragma: no cover
# Upload workout to SportTracks
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid)
@@ -384,6 +323,7 @@ def rower_c2_authorize(request): # pragma: no cover
"redirect_uri": C2_REDIRECT_URI}
url = "http://log.concept2.com/oauth/authorize?"+ urllib.parse.urlencode(params)
url += "&scope="+scope
return HttpResponseRedirect(url)
# Garmin authorization
@@ -429,23 +369,6 @@ def rower_polar_authorize(request): # pragma: no cover
# Runkeeper authorization
@login_required()
def rower_runkeeper_authorize(request): # pragma: no cover
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
state = str(uuid4())
params = {"client_id": RUNKEEPER_CLIENT_ID,
"response_type": "code",
"state": state,
"redirect_uri": RUNKEEPER_REDIRECT_URI}
url = "https://runkeeper.com/apps/authorize?"+ urllib.parse.urlencode(params)
return HttpResponseRedirect(url)
# SportTracks Authorization
@login_required()
@@ -923,31 +846,6 @@ def rower_process_stravacallback(request):
return HttpResponseRedirect(url)
# Process Runkeeper callback
@login_required()
def rower_process_runkeepercallback(request):
code = request.GET['code']
res = runkeeperstuff.get_token(code)
access_token = res[0]
if access_token == 0:# pragma: no cover
messages.error(request,"Something went wrong importing the token")
url = reverse('workouts_view')
return HttpResponseRedirect(url)
r = getrower(request.user)
r.runkeepertoken = access_token
r.save()
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
messages.info(request,successmessage)
url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url)
# Process SportTracks callback
@@ -1498,61 +1396,6 @@ def garmin_details_view(request):
return HttpResponse(status=200)
# The page where you select which RunKeeper workout to import
@login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def workout_runkeeperimport_view(request,message="",userid=0):
res = runkeeperstuff.get_runkeeper_workout_list(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
r = getrower(request.user)
if (r.runkeepertoken == '') or (r.runkeepertoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/runkeeperauthorize/")
message = "Something went wrong in workout_runkeeperimport_view" # pragma: no cover
messages.error(request,message) # pragma: no cover
if settings.DEBUG: # pragma: no cover
return HttpResponse(res)
else: # pragma: no cover
url = reverse('workouts_view')
return HttpResponseRedirect(url)
workouts = []
for item in res.json()['items']:
d = int(float(item['total_distance']))
i = getidfromuri(item['uri'])
ttot = str(datetime.timedelta(seconds=int(float(item['duration']))))
s = item['start_time']
r = item['type']
keys = ['id','distance','duration','starttime','type']
values = [i,d,ttot,s,r]
res = dict(zip(keys,values))
workouts.append(res)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':reverse('workout_runkeeperimport_view'),
'name':'Runkeeper'
}
]
r = getrower(request.user)
return render(request,'runkeeper_list_import.html',
{'workouts':workouts,
'rower':r,
'active':'nav-workouts',
'breadcrumbs':breadcrumbs,
'teams':get_my_teams(request.user),
})
return HttpResponse(res) # pragma: no cover
# the page where you select which Polar workout to Import
@@ -1853,18 +1696,26 @@ importlistviews = {
'strava':'workout_stravaimport_view',
'polar':'workout_polarimport_view',
'ownapi':'workout_view',
'runkeeper':'workout_runkeeperimport_view',
'sporttracks':'workout_sporttracksimport_view',
'trainingpeaks':'workout_view',
'nk':'workout_nkimport_view',
}
importauthorizeviews = {
'c2':'rower_c2_authorize',
'strava':'rower_strava_authorize',
'polar':'rower_polar_authorize',
'ownapi':'workout_view',
'sporttracks':'rower_sporttracks_authorize',
'trainingpeaks':'rower_tp_authorize',
'nk':'rower_nk_authorize',
}
importsources = {
'c2':c2stuff,
'strava':stravastuff,
'polar':polarstuff,
'ownapi':ownapistuff,
'runkeeper':runkeeperstuff,
'sporttracks':sporttracksstuff,
'trainingpeaks':tpstuff,
'nk':nkstuff,
@@ -1894,8 +1745,12 @@ def workout_getrp3importview(request,externalid):
@login_required()
def workout_getimportview(request,externalid,source = 'c2',do_async=True):
result = importsources[source].get_workout(request.user,externalid,
do_async=do_async)
try:
result = importsources[source].get_workout(request.user,externalid,
do_async=do_async)
except NoTokenError:
return HttpResponseRedirect(reverse(importauthorizeviews[source]))
if result: # pragma: no cover
messages.info(request,"Your workout will be imported in the background")
@@ -1919,14 +1774,11 @@ def workout_getsporttracksworkout_all(request):
])
newids = [stid for stid in stids if not stid in knownstids]
for sporttracksid in newids:
data,strokedata = sporttracksstuff.get_workout(
id = sporttracksstuff.get_workout(
request.user,sporttracksid)
id,message = sporttracksstuff.add_workout_from_data(
request.user,sporttracksid,data,strokedata
)
if id==0: # pragma: no cover
messages.error(request,message)
messages.error(request,"Something went wrong with workout {id}".format(id=sporttracksid))
else:
w = Workout.objects.get(id=id)

View File

@@ -158,7 +158,6 @@ import rowers.nkstuff as nkstuff
from rowers.c2stuff import c2_open
from rowers.nkstuff import nk_open
from rowers.rp3stuff import rp3_open
from rowers.runkeeperstuff import runkeeper_open
from rowers.sporttracksstuff import sporttracks_open
from rowers.tpstuff import tp_open
from iso8601 import ParseError
@@ -170,7 +169,6 @@ import rowers.sporttracksstuff as sporttracksstuff
import rowers.tpstuff as tpstuff
import rowers.runkeeperstuff as runkeeperstuff
import rowers.rp3stuff as rp3stuff
import rowers.ownapistuff as ownapistuff
from rowers.ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
@@ -180,7 +178,6 @@ from rowsandall_app.settings import (
POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET,
SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI,
SPORTTRACKS_CLIENT_SECRET,
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
TP_CLIENT_ID,TP_REDIRECT_URI,TP_CLIENT_KEY,TP_CLIENT_SECRET,
RP3_CLIENT_ID,RP3_REDIRECT_URI,RP3_CLIENT_KEY,RP3_CLIENT_SECRET,
BRAINTREE_MERCHANT_ID,BRAINTREE_PUBLIC_KEY,BRAINTREE_PRIVATE_KEY,

View File

@@ -4864,8 +4864,6 @@ def workout_upload_api(request):
upload_to_c2 = optionsform.cleaned_data['upload_to_C2']
upload_to_strava = optionsform.cleaned_data['upload_to_Strava']
upload_to_st = optionsform.cleaned_data['upload_to_SportTracks']
upload_to_rk = optionsform.cleaned_data['upload_to_RunKeeper']
upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness']
upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks']
makeprivate = optionsform.cleaned_data['makeprivate']
else: # pragma: no cover
@@ -5042,8 +5040,6 @@ def workout_upload_view(request,
upload_to_c2 = uploadoptions.get('upload_to_C2',False)
upload_to_strava = uploadoptions.get('upload_to_Strava',False)
upload_to_st = uploadoptions.get('upload_to_SportTracks',False)
upload_to_rk = uploadoptions.get('upload_to_RunKeeper',False)
upload_to_ua = uploadoptions.get('upload_to_MapMyFitness',False)
upload_to_tp = uploadoptions.get('upload_to_TrainingPeaks',False)
response = {}
@@ -5093,8 +5089,6 @@ def workout_upload_view(request,
upload_to_c2 = optionsform.cleaned_data['upload_to_C2']
upload_to_strava = optionsform.cleaned_data['upload_to_Strava']
upload_to_st = optionsform.cleaned_data['upload_to_SportTracks']
upload_to_rk = optionsform.cleaned_data['upload_to_RunKeeper']
upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness']
upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks']
makeprivate = optionsform.cleaned_data['makeprivate']
landingpage = optionsform.cleaned_data['landingpage']
@@ -5112,8 +5106,6 @@ def workout_upload_view(request,
'upload_to_C2':upload_to_c2,
'upload_to_Strava':upload_to_strava,
'upload_to_SportTracks':upload_to_st,
'upload_to_RunKeeper':upload_to_rk,
'upload_to_MapMyFitness':upload_to_ua,
'upload_to_TrainingPeaks':upload_to_tp,
'landingpage':landingpage,
'boattype': boattype,
@@ -5255,20 +5247,6 @@ def workout_upload_view(request,
else:
messages.error(request,message)
if (upload_to_rk): # pragma: no cover
try:
message,id = runkeeperstuff.workout_runkeeper_upload(
request.user,w
)
except NoTokenError:
message = "Please connect to Runkeeper first"
id = 0
if id>1:
messages.info(request,message)
else:
messages.error(request,message)
if (upload_to_tp): # pragma: no cover
try:
@@ -5447,14 +5425,9 @@ def workout_upload_view(request,
if r.sporttracks_auto_export and ispromember(r.user): # pragma: no cover
uploadoptions['upload_to_SportTracks'] = True
if r.runkeeper_auto_export and ispromember(r.user): # pragma: no cover
uploadoptions['upload_to_RunKeeper'] = True
if r.trainingpeaks_auto_export and ispromember(r.user): # pragma: no cover
uploadoptions['upload_to_TrainingPeaks'] = True
if r.mapmyfitness_auto_export and ispromember(r.user): # pragma: no cover
uploadoptions['upload_to_MapMyFitness'] = True
form = DocumentsForm(initial=docformoptions)
optionsform = UploadOptionsForm(initial=uploadoptions,