commit
This commit is contained in:
@@ -1,4 +1,21 @@
|
||||
from __future__ import absolute_import
|
||||
from rowers.tasks import handle_strava_sync, fetch_strava_workout
|
||||
from stravalib.exc import ActivityUploadFailed, TimeoutExceeded
|
||||
from rowers.rower_rules import is_workout_user, ispromember
|
||||
from rowers.utils import get_strava_stream
|
||||
from rowers.utils import dologging
|
||||
from rowers.imports import *
|
||||
from rowsandall_app.settings import (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||
SITE_URL
|
||||
)
|
||||
import gzip
|
||||
import rowers.mytypes as mytypes
|
||||
from rowers.utils import myqueue
|
||||
from iso8601 import ParseError
|
||||
import stravalib
|
||||
from rowers.dataprep import columndict
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
@@ -16,34 +33,12 @@ queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('low')
|
||||
|
||||
from rowers.dataprep import columndict
|
||||
|
||||
from rowers.rower_rules import is_workout_user,ispromember
|
||||
|
||||
import stravalib
|
||||
from stravalib.exc import ActivityUploadFailed,TimeoutExceeded
|
||||
|
||||
from iso8601 import ParseError
|
||||
from rowers.utils import myqueue
|
||||
|
||||
import rowers.mytypes as mytypes
|
||||
import gzip
|
||||
|
||||
from rowers.tasks import handle_strava_sync,fetch_strava_workout
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||
SITE_URL
|
||||
)
|
||||
|
||||
try:
|
||||
from json.decoder import JSONDecodeError
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
JSONDecodeError = ValueError
|
||||
|
||||
from rowers.imports import *
|
||||
from rowers.utils import dologging
|
||||
|
||||
webhookverification = "kudos_to_rowing"
|
||||
webhooklink = SITE_URL+'/rowers/strava/webhooks/'
|
||||
@@ -67,18 +62,19 @@ oauth_data = {
|
||||
'base_url': "https://www.strava.com/oauth/token",
|
||||
'grant_type': 'refresh_token',
|
||||
'headers': headers,
|
||||
'scope':'activity:write,activity:read_all',
|
||||
}
|
||||
'scope': 'activity:write,activity:read_all',
|
||||
}
|
||||
|
||||
|
||||
# Exchange access code for long-lived access token
|
||||
def get_token(code):
|
||||
return imports_get_token(code, oauth_data)
|
||||
|
||||
|
||||
def strava_open(user):
|
||||
t = time.localtime()
|
||||
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
|
||||
with open('strava_open.log','a') as f:
|
||||
with open('strava_open.log', 'a') as f:
|
||||
f.write('\n')
|
||||
f.write(timestamp)
|
||||
f.write(' ')
|
||||
@@ -90,13 +86,15 @@ def strava_open(user):
|
||||
f.write(json.dumps(oauth_data))
|
||||
f.write('\n')
|
||||
token = imports_open(user, oauth_data)
|
||||
if user.rower.strava_owner_id == 0: # pragma: no cover
|
||||
if user.rower.strava_owner_id == 0: # pragma: no cover
|
||||
strava_owner_id = set_strava_athlete_id(user)
|
||||
return token
|
||||
|
||||
|
||||
def do_refresh_token(refreshtoken):
|
||||
return imports_do_refresh_token(refreshtoken, oauth_data)
|
||||
|
||||
|
||||
def rower_strava_token_refresh(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
res = do_refresh_token(r.stravarefreshtoken)
|
||||
@@ -113,10 +111,13 @@ def rower_strava_token_refresh(user):
|
||||
return r.stravatoken
|
||||
|
||||
# Make authorization URL including random string
|
||||
def make_authorization_url(request): # pragma: no cover
|
||||
|
||||
|
||||
def make_authorization_url(request): # pragma: no cover
|
||||
return imports_make_authorization_url(oauth_data)
|
||||
|
||||
def strava_establish_push(): # pragma: no cover
|
||||
|
||||
def strava_establish_push(): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions"
|
||||
post_data = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
@@ -128,41 +129,43 @@ def strava_establish_push(): # pragma: no cover
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': oauth_data['content_type']}
|
||||
|
||||
response = requests.post(url,data=post_data)
|
||||
response = requests.post(url, data=post_data)
|
||||
|
||||
return response.status_code
|
||||
|
||||
def strava_list_push(): # pragma: no cover
|
||||
|
||||
def strava_list_push(): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions"
|
||||
params = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
|
||||
}
|
||||
response = requests.get(url,params=params)
|
||||
response = requests.get(url, params=params)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return [w['id'] for w in data]
|
||||
return []
|
||||
|
||||
def strava_push_delete(id): # pragma: no cover
|
||||
|
||||
def strava_push_delete(id): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions/{id}".format(id=id)
|
||||
params = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
}
|
||||
response = requests.delete(url,json=params)
|
||||
response = requests.delete(url, json=params)
|
||||
return response.status_code
|
||||
|
||||
|
||||
def set_strava_athlete_id(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s)
|
||||
elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599)>r.stravatokenexpirydate):
|
||||
token = imports_open(user,oauth_data)
|
||||
return custom_exception_handler(401, s)
|
||||
elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate):
|
||||
token = imports_open(user, oauth_data)
|
||||
|
||||
authorizationstring = str('Bearer ' + r.stravatoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
@@ -170,9 +173,9 @@ def set_strava_athlete_id(user):
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://www.strava.com/api/v3/athlete"
|
||||
|
||||
response = requests.get(url,headers=headers,params={})
|
||||
response = requests.get(url, headers=headers, params={})
|
||||
|
||||
if response.status_code == 200: # pragma: no cover
|
||||
if response.status_code == 200: # pragma: no cover
|
||||
r.strava_owner_id = response.json()['id']
|
||||
r.save()
|
||||
return response.json()['id']
|
||||
@@ -181,15 +184,15 @@ def set_strava_athlete_id(user):
|
||||
|
||||
|
||||
# Get list of workouts available on Strava
|
||||
def get_strava_workout_list(user,limit_n=0):
|
||||
def get_strava_workout_list(user, limit_n=0):
|
||||
r = Rower.objects.get(user=user)
|
||||
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s)
|
||||
elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599)>r.stravatokenexpirydate): # pragma: no cover
|
||||
return custom_exception_handler(401, s)
|
||||
elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate): # pragma: no cover
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401,s)
|
||||
return custom_exception_handler(401, s)
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.stravatoken)
|
||||
@@ -199,26 +202,24 @@ def get_strava_workout_list(user,limit_n=0):
|
||||
|
||||
url = "https://www.strava.com/api/v3/athlete/activities"
|
||||
|
||||
if limit_n==0:
|
||||
if limit_n == 0:
|
||||
params = {}
|
||||
else: # pragma: no cover
|
||||
params = {'per_page':limit_n}
|
||||
|
||||
s = requests.get(url,headers=headers,params=params)
|
||||
else: # pragma: no cover
|
||||
params = {'per_page': limit_n}
|
||||
|
||||
s = requests.get(url, headers=headers, params=params)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
from rowers.utils import get_strava_stream
|
||||
|
||||
def async_get_workout(user,stravaid):
|
||||
def async_get_workout(user, stravaid):
|
||||
try:
|
||||
token = strava_open(user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
except NoTokenError: # pragma: no cover
|
||||
return 0
|
||||
|
||||
csvfilename = 'media/{code}_{stravaid}.csv'.format(code=uuid4().hex[:16],stravaid=stravaid)
|
||||
csvfilename = 'media/{code}_{stravaid}.csv'.format(
|
||||
code=uuid4().hex[:16], stravaid=stravaid)
|
||||
job = myqueue(queue,
|
||||
fetch_strava_workout,
|
||||
user.rower.stravatoken,
|
||||
@@ -230,16 +231,18 @@ def async_get_workout(user,stravaid):
|
||||
return job
|
||||
|
||||
# Get a Strava workout summary data and stroke data by ID
|
||||
def get_workout(user,stravaid,do_async=True):
|
||||
return async_get_workout(user,stravaid)
|
||||
|
||||
|
||||
def get_workout(user, stravaid, do_async=True):
|
||||
return async_get_workout(user, stravaid)
|
||||
|
||||
|
||||
# Generate Workout data for Strava (a TCX file)
|
||||
def createstravaworkoutdata(w,dozip=True):
|
||||
def createstravaworkoutdata(w, dozip=True):
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except IOError: # pragma: no cover
|
||||
except IOError: # pragma: no cover
|
||||
data = dataprep.read_df_sql(w.id)
|
||||
try:
|
||||
datalength = len(data)
|
||||
@@ -247,16 +250,16 @@ def createstravaworkoutdata(w,dozip=True):
|
||||
datalength = 0
|
||||
|
||||
if datalength != 0:
|
||||
data.rename(columns = columndict,inplace=True)
|
||||
data.rename(columns=columndict, inplace=True)
|
||||
res = data.to_csv(w.csvfilename+'.gz',
|
||||
index_label='index',
|
||||
compression='gzip')
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except IOError:
|
||||
return '','Error - could not find rowing data'
|
||||
return '', 'Error - could not find rowing data'
|
||||
else:
|
||||
return '','Error - could not find rowing data'
|
||||
return '', 'Error - could not find rowing data'
|
||||
|
||||
tcxfilename = filename[:-4]+'.tcx'
|
||||
try:
|
||||
@@ -264,114 +267,115 @@ def createstravaworkoutdata(w,dozip=True):
|
||||
except TypeError:
|
||||
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
|
||||
|
||||
row.exporttotcx(tcxfilename,notes=newnotes)
|
||||
row.exporttotcx(tcxfilename, notes=newnotes)
|
||||
if dozip:
|
||||
gzfilename = tcxfilename+'.gz'
|
||||
with open(tcxfilename,'rb') as inF:
|
||||
with open(tcxfilename, 'rb') as inF:
|
||||
s = inF.read()
|
||||
with gzip.GzipFile(gzfilename,'wb') as outF:
|
||||
with gzip.GzipFile(gzfilename, 'wb') as outF:
|
||||
outF.write(s)
|
||||
|
||||
try:
|
||||
os.remove(tcxfilename)
|
||||
except WindowError: # pragma: no cover
|
||||
except WindowError: # pragma: no cover
|
||||
pass
|
||||
|
||||
return gzfilename,""
|
||||
return gzfilename, ""
|
||||
|
||||
|
||||
return tcxfilename,""
|
||||
return tcxfilename, ""
|
||||
|
||||
|
||||
# Upload the TCX file to Strava and set the workout activity type
|
||||
# to rowing on Strava
|
||||
def handle_stravaexport(f2,workoutname,stravatoken,description='',
|
||||
activity_type='Rowing',quick=False,asynchron=False):
|
||||
def handle_stravaexport(f2, workoutname, stravatoken, description='',
|
||||
activity_type='Rowing', quick=False, asynchron=False):
|
||||
# w = Workout.objects.get(id=workoutid)
|
||||
client = stravalib.Client(access_token=stravatoken)
|
||||
|
||||
act = client.upload_activity(f2,'tcx.gz',name=workoutname)
|
||||
act = client.upload_activity(f2, 'tcx.gz', name=workoutname)
|
||||
|
||||
try:
|
||||
if quick: # pragma: no cover
|
||||
if quick: # pragma: no cover
|
||||
res = act.wait(poll_interval=2.0, timeout=10)
|
||||
message = 'Workout successfully synchronized to Strava'
|
||||
else:
|
||||
res = act.wait(poll_interval=5.0,timeout=30)
|
||||
res = act.wait(poll_interval=5.0, timeout=30)
|
||||
message = 'Workout successfully synchronized to Strava'
|
||||
except: # pragma: no cover
|
||||
except: # pragma: no cover
|
||||
res = 0
|
||||
message = 'Strava upload timed out'
|
||||
|
||||
|
||||
# description doesn't work yet. Have to wait for stravalib to update
|
||||
if res:
|
||||
try:
|
||||
act = client.update_activity(res.id,activity_type=activity_type,description=description,device_name='Rowsandall.com')
|
||||
except TypeError: # pragma: no cover
|
||||
act = client.update_activity(res.id,activity_type=activity_type,description=description)
|
||||
else: # pragma: no cover
|
||||
act = client.update_activity(
|
||||
res.id, activity_type=activity_type, description=description, device_name='Rowsandall.com')
|
||||
except TypeError: # pragma: no cover
|
||||
act = client.update_activity(
|
||||
res.id, activity_type=activity_type, description=description)
|
||||
else: # pragma: no cover
|
||||
message = 'Strava activity update timed out.'
|
||||
return (0,message)
|
||||
return (0, message)
|
||||
|
||||
return (res.id,message)
|
||||
return (res.id, message)
|
||||
|
||||
|
||||
def workout_strava_upload(user,w, quick=False,asynchron=True):
|
||||
def workout_strava_upload(user, w, quick=False, asynchron=True):
|
||||
try:
|
||||
thetoken = strava_open(user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
return "Please connect to Strava first",0
|
||||
except NoTokenError: # pragma: no cover
|
||||
return "Please connect to Strava first", 0
|
||||
|
||||
message = "Uploading to Strava"
|
||||
stravaid=-1
|
||||
stravaid = -1
|
||||
r = Rower.objects.get(user=user)
|
||||
res = -1
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
raise NoTokenError("Your hovercraft is full of eels")
|
||||
|
||||
if (is_workout_user(user,w)):
|
||||
if (is_workout_user(user, w)):
|
||||
if asynchron:
|
||||
tcxfile, tcxmesg = createstravaworkoutdata(w)
|
||||
if not tcxfile: # pragma: no cover
|
||||
return "Failed to create workout data",0
|
||||
if not tcxfile: # pragma: no cover
|
||||
return "Failed to create workout data", 0
|
||||
activity_type = r.stravaexportas
|
||||
if r.stravaexportas == 'match':
|
||||
try:
|
||||
activity_type = mytypes.stravamapping[w.workouttype]
|
||||
except KeyError: # pragma: no cover
|
||||
except KeyError: # pragma: no cover
|
||||
activity_type = 'Rowing'
|
||||
job = myqueue(queue,
|
||||
handle_strava_sync,
|
||||
r.stravatoken,
|
||||
w.id,
|
||||
tcxfile,w.name,activity_type,
|
||||
tcxfile, w.name, activity_type,
|
||||
w.notes
|
||||
)
|
||||
dologging('strava_export_log.log','Exporting as {t} from {w}'.format(t=activity_type,w=w.workouttype))
|
||||
return "Asynchronous sync",-1
|
||||
dologging('strava_export_log.log', 'Exporting as {t} from {w}'.format(
|
||||
t=activity_type, w=w.workouttype))
|
||||
return "Asynchronous sync", -1
|
||||
try:
|
||||
tcxfile,tcxmesg = createstravaworkoutdata(w)
|
||||
tcxfile, tcxmesg = createstravaworkoutdata(w)
|
||||
if tcxfile:
|
||||
activity_type = r.stravaexportas
|
||||
if r.stravaexportas == 'match':
|
||||
try:
|
||||
activity_type = mytypes.stravamapping[w.workouttype]
|
||||
except KeyError: # pragma: no cover
|
||||
except KeyError: # pragma: no cover
|
||||
activity_type = 'Rowing'
|
||||
|
||||
with open(tcxfile,'rb') as f:
|
||||
with open(tcxfile, 'rb') as f:
|
||||
try:
|
||||
description = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
|
||||
except TypeError:
|
||||
description = ' via rowsandall.com'
|
||||
res,mes = handle_stravaexport(
|
||||
f,w.name,
|
||||
res, mes = handle_stravaexport(
|
||||
f, w.name,
|
||||
r.stravatoken,
|
||||
description=description,
|
||||
activity_type=activity_type,quick=quick,asynchron=asynchron)
|
||||
if res==0: # pragma: no cover
|
||||
activity_type=activity_type, quick=quick, asynchron=asynchron)
|
||||
if res == 0: # pragma: no cover
|
||||
message = mes
|
||||
w.uploadedtostrava = -1
|
||||
stravaid = -1
|
||||
@@ -380,29 +384,29 @@ def workout_strava_upload(user,w, quick=False,asynchron=True):
|
||||
os.remove(tcxfile)
|
||||
except WindowsError:
|
||||
pass
|
||||
return message,stravaid
|
||||
return message, stravaid
|
||||
|
||||
w.uploadedtostrava = res
|
||||
w.save()
|
||||
try:
|
||||
os.remove(tcxfile)
|
||||
except WindowsError: # pragma: no cover
|
||||
except WindowsError: # pragma: no cover
|
||||
pass
|
||||
message = mes
|
||||
stravaid = res
|
||||
return message,stravaid
|
||||
else: # pragma: no cover
|
||||
return message, stravaid
|
||||
else: # pragma: no cover
|
||||
message = "Strava TCX data error "+tcxmesg
|
||||
w.uploadedtostrava = -1
|
||||
stravaid = -1
|
||||
w.save()
|
||||
return message, stravaid
|
||||
|
||||
except ActivityUploadFailed as e: # pragma: no cover
|
||||
except ActivityUploadFailed as e: # pragma: no cover
|
||||
message = "Strava Upload error: %s" % e
|
||||
w.uploadedtostrava = -1
|
||||
stravaid = -1
|
||||
w.save()
|
||||
os.remove(tcxfile)
|
||||
return message,stravaid
|
||||
return message,stravaid # pragma: no cover
|
||||
return message, stravaid
|
||||
return message, stravaid # pragma: no cover
|
||||
|
||||
Reference in New Issue
Block a user