adding strava
This commit is contained in:
@@ -1 +1,2 @@
|
||||
from .c2 import C2Integration
|
||||
from .strava import StravaIntegration
|
||||
|
||||
@@ -5,7 +5,7 @@ import numpy as np
|
||||
import datetime
|
||||
import json
|
||||
import urllib
|
||||
from rowers.utils import dologging, uniqify
|
||||
from rowers.utils import dologging, uniqify, custom_exception_handler
|
||||
from django.utils import timezone
|
||||
import requests
|
||||
from pytz.exceptions import UnknownTimeZoneError
|
||||
@@ -432,7 +432,7 @@ class C2Integration(SyncIntegration):
|
||||
else: # pragma: no cover
|
||||
nnn = 'NEW'
|
||||
keys = ['id', 'distance', 'duration', 'starttime',
|
||||
'rowtype', 'source', 'comment', 'new']
|
||||
'rowtype', 'source', 'name', 'new']
|
||||
values = [i, d, ttot, s, r, s2, c, nnn]
|
||||
ress = dict(zip(keys, values))
|
||||
workouts.append(ress)
|
||||
@@ -444,7 +444,3 @@ class C2Integration(SyncIntegration):
|
||||
|
||||
|
||||
|
||||
# just as a quick test during development
|
||||
u = User.objects.get(id=1)
|
||||
|
||||
c2_integration_1 = C2Integration(u)
|
||||
|
||||
@@ -38,8 +38,8 @@ class SyncIntegration(metaclass=ABCMeta):
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def createworkoutdata(w, *args, **kwargs) -> dict:
|
||||
return {}
|
||||
def createworkoutdata(w, *args, **kwargs):
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
def workout_export(workout, *args, **kwargs) -> str:
|
||||
@@ -51,12 +51,12 @@ class SyncIntegration(metaclass=ABCMeta):
|
||||
|
||||
@abstractmethod
|
||||
def get_workout(id) -> int:
|
||||
pass
|
||||
return 0
|
||||
|
||||
# need to unify workout list
|
||||
@abstractmethod
|
||||
def get_workout_list(*args, **kwargs) -> list:
|
||||
pass
|
||||
return []
|
||||
|
||||
@abstractmethod
|
||||
def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover
|
||||
|
||||
344
rowers/integrations/strava.py
Normal file
344
rowers/integrations/strava.py
Normal file
@@ -0,0 +1,344 @@
|
||||
from .integrations import SyncIntegration, NoTokenError
|
||||
from rowers.models import User, Rower, Workout, TombStone
|
||||
from rowingdata import rowingdata
|
||||
|
||||
from rowers import mytypes
|
||||
|
||||
from rowers.tasks import handle_strava_sync, fetch_strava_workout
|
||||
from stravalib.exc import ActivityUploadFailed, TimeoutExceeded
|
||||
from rowers.rower_rules import is_workout_user, ispromember
|
||||
from rowers.utils import get_strava_stream
|
||||
|
||||
from rowers.utils import myqueue, dologging
|
||||
from rowers.imports import *
|
||||
|
||||
import gzip
|
||||
import time
|
||||
import requests
|
||||
import arrow
|
||||
import datetime
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||
SITE_URL
|
||||
)
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('high')
|
||||
|
||||
webhookverification = "kudos_to_rowing"
|
||||
webhooklink = SITE_URL+'/rowers/strava/webhooks/'
|
||||
|
||||
headers = {'Accept': 'application/json',
|
||||
'Api-Key': STRAVA_CLIENT_ID,
|
||||
'Content-Type': 'application/json',
|
||||
'user-agent': 'sanderroosendaal'}
|
||||
|
||||
from json.decoder import JSONDecodeError
|
||||
from rowers.dataprep import columndict
|
||||
|
||||
def strava_establish_push(): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions"
|
||||
post_data = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
'callback_url': webhooklink,
|
||||
'verify_token': webhookverification,
|
||||
}
|
||||
|
||||
response = requests.post(url, data=post_data)
|
||||
|
||||
return response.status_code
|
||||
|
||||
|
||||
def strava_list_push(): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions"
|
||||
params = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
|
||||
}
|
||||
response = requests.get(url, params=params)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return [w['id'] for w in data]
|
||||
return []
|
||||
|
||||
|
||||
def strava_push_delete(id): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions/{id}".format(id=id)
|
||||
params = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
}
|
||||
response = requests.delete(url, json=params)
|
||||
return response.status_code
|
||||
|
||||
|
||||
class StravaIntegration(SyncIntegration):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(StravaIntegration, self).__init__(self, *args, **kwargs)
|
||||
self.oauth_data = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
'redirect_uri': STRAVA_REDIRECT_URI,
|
||||
'autorization_uri': "https://www.strava.com/oauth/authorize",
|
||||
'content_type': 'application/json',
|
||||
'tokenname': 'stravatoken',
|
||||
'refreshtokenname': 'stravarefreshtoken',
|
||||
'expirydatename': 'stravatokenexpirydate',
|
||||
'bearer_auth': True,
|
||||
'base_url': "https://www.strava.com/oauth/token",
|
||||
'grant_type': 'refresh_token',
|
||||
'headers': headers,
|
||||
'scope': 'activity:write,activity:read_all',
|
||||
}
|
||||
|
||||
def get_token(self, code, *args, **kwargs):
|
||||
return super(StravaIntegration, self).get_token(code, *args, **kwargs)
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
dologging('strava_log.log','Getting token for user {id}'.format(id=self.rower.id))
|
||||
token = super(StravaIntegration, self).open(*args, **kwargs)
|
||||
if self.rower.strava_owner_id == 0:
|
||||
_ = self.set_strava_athlete_id()
|
||||
|
||||
return token
|
||||
|
||||
# createworkoutdata
|
||||
def createworkoutdata(self, w, *args, **kwargs) -> str:
|
||||
dozip = kwargs.get('dozip', True)
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except IOError: # pragma: no cover
|
||||
data = dataprep.read_df_sql(w.id)
|
||||
try:
|
||||
datalength = len(data)
|
||||
except AttributeError:
|
||||
datalength = 0
|
||||
|
||||
if datalength != 0:
|
||||
data.rename(columns=columndict, inplace=True)
|
||||
_ = data.to_csv(w.csvfilename+'.gz',
|
||||
index_label='index',
|
||||
compression='gzip')
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except IOError:
|
||||
return ''
|
||||
else:
|
||||
return ''
|
||||
|
||||
tcxfilename = filename[:-4]+'.tcx'
|
||||
try:
|
||||
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
|
||||
except TypeError:
|
||||
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
|
||||
|
||||
row.exporttotcx(tcxfilename, notes=newnotes)
|
||||
if dozip:
|
||||
gzfilename = tcxfilename+'.gz'
|
||||
with open(tcxfilename, 'rb') as inF:
|
||||
s = inF.read()
|
||||
with gzip.GzipFile(gzfilename, 'wb') as outF:
|
||||
outF.write(s)
|
||||
|
||||
try:
|
||||
os.remove(tcxfilename)
|
||||
except WindowError: # pragma: no cover
|
||||
pass
|
||||
|
||||
return gzfilename
|
||||
|
||||
return tcxfilename
|
||||
|
||||
|
||||
# workout_export
|
||||
def workout_export(self, workout, *args, **kwargs) -> str:
|
||||
description = kwargs.get('description','')
|
||||
quick = kwargs.get('quick',False)
|
||||
try:
|
||||
_ = self.open()
|
||||
except NoTokenError:
|
||||
return 0
|
||||
|
||||
if (self.rower.stravatoken == '') or (self.rower.stravatoken is None):
|
||||
raise NoTokenError("Your hovercraft is full of eels")
|
||||
|
||||
if not (is_workout_user(self.user, workout)):
|
||||
return 0
|
||||
|
||||
tcxfile = self.createworkoutdata(workout)
|
||||
if not tcxfile:
|
||||
return 0
|
||||
activity_type = self.rower.stravaexportas
|
||||
if activity_type == 'match':
|
||||
try:
|
||||
activity_type = mytypes.stravamapping[workout.workouttype]
|
||||
except KeyError:
|
||||
activity_type = 'Rowing'
|
||||
|
||||
_ = myqueue(queue,
|
||||
handle_strava_sync,
|
||||
self.rower.stravatoken,
|
||||
workout.id,
|
||||
tcxfile, workout.name, activity_type,
|
||||
workout.notes)
|
||||
|
||||
dologging('strava_export_log.log', 'Exporting as {t} from {w}'.format(
|
||||
t=activity_type, w=workout.workouttype))
|
||||
|
||||
return 1
|
||||
|
||||
# get_workouts
|
||||
def get_workouts(workout, *args, **kwargs) -> int:
|
||||
return NotImplemented
|
||||
|
||||
# get_workout
|
||||
def get_workout(self, id) -> int:
|
||||
try:
|
||||
_ = self.open()
|
||||
except NoTokenError:
|
||||
return 0
|
||||
csvfilename = 'media/{code}_{stravaid}.csv'.format(
|
||||
|
||||
code=uuid4().hex[:16], stravaid=stravaid)
|
||||
job = myqueue(queue,
|
||||
fetch_strava_workout,
|
||||
self.rower.stravatoken,
|
||||
oauth_data,
|
||||
id,
|
||||
csvfilename,
|
||||
self.user.id,
|
||||
)
|
||||
|
||||
return job.id
|
||||
|
||||
|
||||
# get_workout_list
|
||||
def get_workout_list(self, *args, **kwargs) -> list:
|
||||
limit_n = kwargs.get('limit_n',0)
|
||||
|
||||
if (self.rower.stravatoken == '') or (self.rower.stravatoken is None): # pragma: no cover
|
||||
raise NoTokenError
|
||||
elif (self.rower.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > self.rower.stravatokenexpirydate): # pragma: no cover
|
||||
raise NoTokenError
|
||||
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + self.rower.stravatoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
|
||||
url = "https://www.strava.com/api/v3/athlete/activities"
|
||||
|
||||
if limit_n == 0:
|
||||
params = {}
|
||||
else: # pragma: no cover
|
||||
params = {'per_page': limit_n}
|
||||
|
||||
res = requests.get(url, headers=headers, params=params)
|
||||
|
||||
if (res.status_code != 200): # pragma: no cover
|
||||
return []
|
||||
|
||||
workouts = []
|
||||
rower = self.rower
|
||||
stravaids = [int(item['id']) for item in res.json()]
|
||||
stravadata = [{
|
||||
'id': int(item['id']),
|
||||
'elapsed_time':item['elapsed_time'],
|
||||
'start_date':item['start_date'],
|
||||
} for item in res.json()]
|
||||
|
||||
wfailed = Workout.objects.filter(user=rower, uploadedtostrava=-1)
|
||||
|
||||
for w in wfailed: # pragma: no cover
|
||||
for item in stravadata:
|
||||
elapsed_time = item['elapsed_time']
|
||||
start_date = item['start_date']
|
||||
stravaid = item['id']
|
||||
if arrow.get(start_date) == arrow.get(w.startdatetime):
|
||||
elapsed_td = datetime.timedelta(seconds=int(elapsed_time))
|
||||
elapsed_time = datetime.datetime.strptime(
|
||||
str(elapsed_td),
|
||||
"%H:%M:%S"
|
||||
)
|
||||
if str(elapsed_time)[-7:] == str(w.duration)[-7:]:
|
||||
w.uploadedtostrava = int(stravaid)
|
||||
w.save()
|
||||
|
||||
knownstravaids = uniqify([
|
||||
w.uploadedtostrava for w in Workout.objects.filter(user=self.rower)
|
||||
])
|
||||
|
||||
for item in res.json():
|
||||
d = int(float(item['distance']))
|
||||
i = item['id']
|
||||
if i in knownstravaids: # pragma: no cover
|
||||
nnn = ''
|
||||
else:
|
||||
nnn = 'NEW'
|
||||
n = item['name']
|
||||
ttot = str(datetime.timedelta(
|
||||
seconds=int(float(item['elapsed_time']))))
|
||||
s = item['start_date']
|
||||
r = item['type']
|
||||
s2 = None
|
||||
keys = ['id', 'distance', 'duration',
|
||||
'starttime', 'rowtype', 'source', 'name', 'new']
|
||||
values = [i, d, ttot, s, r, s2, n, nnn]
|
||||
res2 = dict(zip(keys, values))
|
||||
workouts.append(res2)
|
||||
|
||||
return workouts
|
||||
|
||||
|
||||
# make_authorization_url
|
||||
def make_authorization_url(self, *args, **kwargs):
|
||||
params = {"client_id": STRAVA_CLIENT_ID,
|
||||
"response_type": "code",
|
||||
"redirect_uri": STRAVA_REDIRECT_URI,
|
||||
"scope": "activity:write,activity:read_all"}
|
||||
|
||||
url = "https://www.strava.com/oauth/authorize?" + \
|
||||
urllib.parse.urlencode(params)
|
||||
|
||||
return url
|
||||
|
||||
# token_refresh
|
||||
def token_refresh(self, *args, **kwargs):
|
||||
return super(StravaIntegration).token_refresh(*args, **kwargs)
|
||||
|
||||
def set_strava_athlete_id():
|
||||
r = self.rower()
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401, s)
|
||||
elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate):
|
||||
_ = self.open()
|
||||
|
||||
authorizationstring = str('Bearer ' + r.stravatoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://www.strava.com/api/v3/athlete"
|
||||
|
||||
response = requests.get(url, headers=headers, params={})
|
||||
|
||||
if response.status_code == 200: # pragma: no cover
|
||||
r.strava_owner_id = response.json()['id']
|
||||
r.save()
|
||||
return response.json()['id']
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
# just as a quick test during development
|
||||
u = User.objects.get(id=1)
|
||||
|
||||
strava_integration_1 = StravaIntegration(u)
|
||||
@@ -17,7 +17,8 @@ import rowers.c2stuff as c2stuff
|
||||
import rowers.metrics as metrics
|
||||
import rowers.dataprep as dataprep
|
||||
from rowers.dataprep import rdata
|
||||
import rowers.stravastuff as stravastuff
|
||||
import rowers.utils as utils
|
||||
|
||||
from scipy.interpolate import griddata
|
||||
from scipy.signal import savgol_filter
|
||||
from scipy import optimize
|
||||
@@ -5401,7 +5402,7 @@ def interactive_flexchart_stacked(id, r, xparam='time',
|
||||
if metricsdicts[column]['maysmooth']:
|
||||
nrsteps = int(log2(r.usersmooth))
|
||||
for i in range(nrsteps):
|
||||
rowdata[column] = stravastuff.ewmovingaverage(
|
||||
rowdata[column] = utils.ewmovingaverage(
|
||||
rowdata[column], 5)
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -5767,7 +5768,7 @@ def interactive_flex_chart2(id, r, promember=0,
|
||||
if metricsdicts[column]['maysmooth']:
|
||||
nrsteps = int(log2(r.usersmooth))
|
||||
for i in range(nrsteps):
|
||||
rowdata[column] = stravastuff.ewmovingaverage(
|
||||
rowdata[column] = utils.ewmovingaverage(
|
||||
rowdata[column], 5)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@@ -30,7 +30,7 @@ import rowers.uploads as uploads
|
||||
import rowers.polarstuff as polarstuff
|
||||
|
||||
import rowers.rp3stuff as rp3stuff
|
||||
import rowers.stravastuff as stravastuff
|
||||
|
||||
import rowers.nkstuff as nkstuff
|
||||
from rowers.opaque import encoder
|
||||
from rowers.integrations import *
|
||||
|
||||
@@ -1,404 +0,0 @@
|
||||
from rowers.tasks import handle_strava_sync, fetch_strava_workout
|
||||
from stravalib.exc import ActivityUploadFailed, TimeoutExceeded
|
||||
from rowers.rower_rules import is_workout_user, ispromember
|
||||
from rowers.utils import get_strava_stream
|
||||
from rowers.utils import dologging
|
||||
from rowers.imports import *
|
||||
from rowsandall_app.settings import (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||
SITE_URL
|
||||
)
|
||||
import gzip
|
||||
import rowers.mytypes as mytypes
|
||||
from rowers.utils import myqueue
|
||||
from iso8601 import ParseError
|
||||
import stravalib
|
||||
from rowers.dataprep import columndict
|
||||
|
||||
# All the functionality needed to connect to Strava
|
||||
from scipy import optimize
|
||||
from scipy.signal import savgol_filter
|
||||
import time
|
||||
from time import strftime
|
||||
|
||||
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('low')
|
||||
|
||||
|
||||
try:
|
||||
from json.decoder import JSONDecodeError
|
||||
except ImportError: # pragma: no cover
|
||||
JSONDecodeError = ValueError
|
||||
|
||||
|
||||
webhookverification = "kudos_to_rowing"
|
||||
webhooklink = SITE_URL+'/rowers/strava/webhooks/'
|
||||
|
||||
headers = {'Accept': 'application/json',
|
||||
'Api-Key': STRAVA_CLIENT_ID,
|
||||
'Content-Type': 'application/json',
|
||||
'user-agent': 'sanderroosendaal'}
|
||||
|
||||
|
||||
oauth_data = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
'redirect_uri': STRAVA_REDIRECT_URI,
|
||||
'autorization_uri': "https://www.strava.com/oauth/authorize",
|
||||
'content_type': 'application/json',
|
||||
'tokenname': 'stravatoken',
|
||||
'refreshtokenname': 'stravarefreshtoken',
|
||||
'expirydatename': 'stravatokenexpirydate',
|
||||
'bearer_auth': True,
|
||||
'base_url': "https://www.strava.com/oauth/token",
|
||||
'grant_type': 'refresh_token',
|
||||
'headers': headers,
|
||||
'scope': 'activity:write,activity:read_all',
|
||||
}
|
||||
|
||||
|
||||
# Exchange access code for long-lived access token
|
||||
def get_token(code):
|
||||
return imports_get_token(code, oauth_data)
|
||||
|
||||
|
||||
def strava_open(user):
|
||||
t = time.localtime()
|
||||
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
|
||||
with open('strava_open.log', 'a') as f:
|
||||
f.write('\n')
|
||||
f.write(timestamp)
|
||||
f.write(' ')
|
||||
f.write('Getting token for user ')
|
||||
f.write(str(user.rower.id))
|
||||
f.write(' token expiry ')
|
||||
f.write(str(user.rower.stravatokenexpirydate))
|
||||
f.write(' ')
|
||||
f.write(json.dumps(oauth_data))
|
||||
f.write('\n')
|
||||
token = imports_open(user, oauth_data)
|
||||
if user.rower.strava_owner_id == 0: # pragma: no cover
|
||||
_ = set_strava_athlete_id(user)
|
||||
return token
|
||||
|
||||
|
||||
def do_refresh_token(refreshtoken):
|
||||
return imports_do_refresh_token(refreshtoken, oauth_data)
|
||||
|
||||
|
||||
def rower_strava_token_refresh(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
res = do_refresh_token(r.stravarefreshtoken)
|
||||
access_token = res[0]
|
||||
expires_in = res[1]
|
||||
refresh_token = res[2]
|
||||
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
|
||||
|
||||
r.stravatoken = access_token
|
||||
r.stravatokenexpirydate = expirydatetime
|
||||
r.stravarefreshtoken = refresh_token
|
||||
r.save()
|
||||
|
||||
return r.stravatoken
|
||||
|
||||
# Make authorization URL including random string
|
||||
|
||||
|
||||
def make_authorization_url(request): # pragma: no cover
|
||||
return imports_make_authorization_url(oauth_data)
|
||||
|
||||
|
||||
def strava_establish_push(): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions"
|
||||
post_data = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
'callback_url': webhooklink,
|
||||
'verify_token': webhookverification,
|
||||
}
|
||||
|
||||
response = requests.post(url, data=post_data)
|
||||
|
||||
return response.status_code
|
||||
|
||||
|
||||
def strava_list_push(): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions"
|
||||
params = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
|
||||
}
|
||||
response = requests.get(url, params=params)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return [w['id'] for w in data]
|
||||
return []
|
||||
|
||||
|
||||
def strava_push_delete(id): # pragma: no cover
|
||||
url = "https://www.strava.com/api/v3/push_subscriptions/{id}".format(id=id)
|
||||
params = {
|
||||
'client_id': STRAVA_CLIENT_ID,
|
||||
'client_secret': STRAVA_CLIENT_SECRET,
|
||||
}
|
||||
response = requests.delete(url, json=params)
|
||||
return response.status_code
|
||||
|
||||
|
||||
def set_strava_athlete_id(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401, s)
|
||||
elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate):
|
||||
_ = imports_open(user, oauth_data)
|
||||
|
||||
authorizationstring = str('Bearer ' + r.stravatoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://www.strava.com/api/v3/athlete"
|
||||
|
||||
response = requests.get(url, headers=headers, params={})
|
||||
|
||||
if response.status_code == 200: # pragma: no cover
|
||||
r.strava_owner_id = response.json()['id']
|
||||
r.save()
|
||||
return response.json()['id']
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
# Get list of workouts available on Strava
|
||||
def get_strava_workout_list(user, limit_n=0):
|
||||
r = Rower.objects.get(user=user)
|
||||
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401, s)
|
||||
elif (
|
||||
r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate
|
||||
): # pragma: no cover
|
||||
s = "Token expired. Needs to refresh."
|
||||
return custom_exception_handler(401, s)
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.stravatoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
|
||||
url = "https://www.strava.com/api/v3/athlete/activities"
|
||||
|
||||
if limit_n == 0:
|
||||
params = {}
|
||||
else: # pragma: no cover
|
||||
params = {'per_page': limit_n}
|
||||
|
||||
s = requests.get(url, headers=headers, params=params)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def async_get_workout(user, stravaid):
|
||||
try:
|
||||
_ = strava_open(user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
return 0
|
||||
|
||||
csvfilename = 'media/{code}_{stravaid}.csv'.format(
|
||||
code=uuid4().hex[:16], stravaid=stravaid)
|
||||
job = myqueue(queue,
|
||||
fetch_strava_workout,
|
||||
user.rower.stravatoken,
|
||||
oauth_data,
|
||||
stravaid,
|
||||
csvfilename,
|
||||
user.id,
|
||||
)
|
||||
return job
|
||||
|
||||
# Get a Strava workout summary data and stroke data by ID
|
||||
|
||||
|
||||
def get_workout(user, stravaid, do_async=True):
|
||||
return async_get_workout(user, stravaid)
|
||||
|
||||
|
||||
# Generate Workout data for Strava (a TCX file)
|
||||
def createstravaworkoutdata(w, dozip=True):
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except IOError: # pragma: no cover
|
||||
data = dataprep.read_df_sql(w.id)
|
||||
try:
|
||||
datalength = len(data)
|
||||
except AttributeError:
|
||||
datalength = 0
|
||||
|
||||
if datalength != 0:
|
||||
data.rename(columns=columndict, inplace=True)
|
||||
_ = data.to_csv(w.csvfilename+'.gz',
|
||||
index_label='index',
|
||||
compression='gzip')
|
||||
try:
|
||||
row = rowingdata(csvfile=filename)
|
||||
except IOError:
|
||||
return '', 'Error - could not find rowing data'
|
||||
else:
|
||||
return '', 'Error - could not find rowing data'
|
||||
|
||||
tcxfilename = filename[:-4]+'.tcx'
|
||||
try:
|
||||
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
|
||||
except TypeError:
|
||||
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
|
||||
|
||||
row.exporttotcx(tcxfilename, notes=newnotes)
|
||||
if dozip:
|
||||
gzfilename = tcxfilename+'.gz'
|
||||
with open(tcxfilename, 'rb') as inF:
|
||||
s = inF.read()
|
||||
with gzip.GzipFile(gzfilename, 'wb') as outF:
|
||||
outF.write(s)
|
||||
|
||||
try:
|
||||
os.remove(tcxfilename)
|
||||
except WindowError: # pragma: no cover
|
||||
pass
|
||||
|
||||
return gzfilename, ""
|
||||
|
||||
return tcxfilename, ""
|
||||
|
||||
|
||||
# Upload the TCX file to Strava and set the workout activity type
|
||||
# to rowing on Strava
|
||||
def handle_stravaexport(f2, workoutname, stravatoken, description='',
|
||||
activity_type='Rowing', quick=False, asynchron=False):
|
||||
# w = Workout.objects.get(id=workoutid)
|
||||
client = stravalib.Client(access_token=stravatoken)
|
||||
|
||||
act = client.upload_activity(f2, 'tcx.gz', name=workoutname)
|
||||
|
||||
try:
|
||||
if quick: # pragma: no cover
|
||||
res = act.wait(poll_interval=2.0, timeout=10)
|
||||
message = 'Workout successfully synchronized to Strava'
|
||||
else:
|
||||
res = act.wait(poll_interval=5.0, timeout=30)
|
||||
message = 'Workout successfully synchronized to Strava'
|
||||
except: # pragma: no cover
|
||||
res = 0
|
||||
message = 'Strava upload timed out'
|
||||
|
||||
# description doesn't work yet. Have to wait for stravalib to update
|
||||
if res:
|
||||
try:
|
||||
act = client.update_activity(
|
||||
res.id, activity_type=activity_type, description=description, device_name='Rowsandall.com')
|
||||
except TypeError: # pragma: no cover
|
||||
act = client.update_activity(
|
||||
res.id, activity_type=activity_type, description=description)
|
||||
else: # pragma: no cover
|
||||
message = 'Strava activity update timed out.'
|
||||
return (0, message)
|
||||
|
||||
return (res.id, message)
|
||||
|
||||
|
||||
def workout_strava_upload(user, w, quick=False, asynchron=True):
|
||||
try:
|
||||
_ = strava_open(user)
|
||||
except NoTokenError: # pragma: no cover
|
||||
return "Please connect to Strava first", 0
|
||||
|
||||
message = "Uploading to Strava"
|
||||
stravaid = -1
|
||||
r = Rower.objects.get(user=user)
|
||||
res = -1
|
||||
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
|
||||
raise NoTokenError("Your hovercraft is full of eels")
|
||||
|
||||
if (is_workout_user(user, w)):
|
||||
if asynchron:
|
||||
tcxfile, tcxmesg = createstravaworkoutdata(w)
|
||||
if not tcxfile: # pragma: no cover
|
||||
return "Failed to create workout data", 0
|
||||
activity_type = r.stravaexportas
|
||||
if r.stravaexportas == 'match':
|
||||
try:
|
||||
activity_type = mytypes.stravamapping[w.workouttype]
|
||||
except KeyError: # pragma: no cover
|
||||
activity_type = 'Rowing'
|
||||
_ = myqueue(queue,
|
||||
handle_strava_sync,
|
||||
r.stravatoken,
|
||||
w.id,
|
||||
tcxfile, w.name, activity_type,
|
||||
w.notes)
|
||||
dologging('strava_export_log.log', 'Exporting as {t} from {w}'.format(
|
||||
t=activity_type, w=w.workouttype))
|
||||
return "Asynchronous sync", -1
|
||||
try:
|
||||
tcxfile, tcxmesg = createstravaworkoutdata(w)
|
||||
if tcxfile:
|
||||
activity_type = r.stravaexportas
|
||||
if r.stravaexportas == 'match':
|
||||
try:
|
||||
activity_type = mytypes.stravamapping[w.workouttype]
|
||||
except KeyError: # pragma: no cover
|
||||
activity_type = 'Rowing'
|
||||
|
||||
with open(tcxfile, 'rb') as f:
|
||||
try:
|
||||
description = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
|
||||
except TypeError:
|
||||
description = ' via rowsandall.com'
|
||||
res, mes = handle_stravaexport(
|
||||
f, w.name,
|
||||
r.stravatoken,
|
||||
description=description,
|
||||
activity_type=activity_type, quick=quick, asynchron=asynchron)
|
||||
if res == 0: # pragma: no cover
|
||||
message = mes
|
||||
w.uploadedtostrava = -1
|
||||
stravaid = -1
|
||||
w.save()
|
||||
try:
|
||||
os.remove(tcxfile)
|
||||
except WindowsError:
|
||||
pass
|
||||
return message, stravaid
|
||||
|
||||
w.uploadedtostrava = res
|
||||
w.save()
|
||||
try:
|
||||
os.remove(tcxfile)
|
||||
except WindowsError: # pragma: no cover
|
||||
pass
|
||||
message = mes
|
||||
stravaid = res
|
||||
return message, stravaid
|
||||
else: # pragma: no cover
|
||||
message = "Strava TCX data error "+tcxmesg
|
||||
w.uploadedtostrava = -1
|
||||
stravaid = -1
|
||||
w.save()
|
||||
return message, stravaid
|
||||
|
||||
except ActivityUploadFailed as e: # pragma: no cover
|
||||
message = "Strava Upload error: %s" % e
|
||||
w.uploadedtostrava = -1
|
||||
stravaid = -1
|
||||
w.save()
|
||||
os.remove(tcxfile)
|
||||
return message, stravaid
|
||||
return message, stravaid # pragma: no cover
|
||||
@@ -9,11 +9,12 @@
|
||||
|
||||
<ul class="main-content">
|
||||
{% if workouts %}
|
||||
{% if integration == 'C2 Logbook' %}
|
||||
<li class="grid_2">
|
||||
<a href="/rowers/workout/c2import/all/{{ page }}">Import all NEW</a>
|
||||
<p>This imports all workouts that have not been imported to rowsandall.com.
|
||||
The action may take a longer time to process, so please be patient. Click on Import in the list below to import an individual workout.
|
||||
</p>
|
||||
</p>
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<p>
|
||||
@@ -29,11 +30,12 @@
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="grid_4">
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
{% csrf_token %}
|
||||
<input name='workouts' type="submit" value="Import selected workouts">
|
||||
<a href="/rowers/workout/c2list/?selectallnew=true">Select All New</a>
|
||||
<a href="?selectallnew=true">Select All New</a>
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -43,7 +45,7 @@
|
||||
<th> Total Distance</th>
|
||||
<th> Type</th>
|
||||
<th> Source</th>
|
||||
<th> Comment</th>
|
||||
<th> Name</th>
|
||||
<th> New</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -62,7 +64,7 @@
|
||||
<td>{{ workout|lookup:'distance' }}</td>
|
||||
<td>{{ workout|lookup:'rowtype' }}</td>
|
||||
<td>{{ workout|lookup:'source' }}</td>
|
||||
<td>{{ workout|lookup:'comment' }}</td>
|
||||
<td>{{ workout|lookup:'name' }}</td>
|
||||
<td>
|
||||
{{ workout|lookup:'new' }}
|
||||
</td>
|
||||
|
||||
@@ -522,7 +522,7 @@ def deltatimeprint(d): # pragma: no cover
|
||||
def c2userid(user): # pragma: no cover
|
||||
c2integration = C2Integration(user)
|
||||
|
||||
c2userid = c2integration.get_userid(thetoken)
|
||||
c2userid = c2integration.get_userid(user)
|
||||
|
||||
return c2userid
|
||||
|
||||
@@ -566,6 +566,8 @@ def lookup(dict, key):
|
||||
s = dict.get(key)
|
||||
except KeyError: # pragma: no cover
|
||||
return None
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
if isinstance(s, string_types) and len(s) > 22:
|
||||
s = s[:22]
|
||||
|
||||
@@ -14,7 +14,7 @@ import rowers
|
||||
from rowers import dataprep
|
||||
from rowers import tasks
|
||||
from rowers import c2stuff
|
||||
from rowers import stravastuff
|
||||
|
||||
import urllib
|
||||
import json
|
||||
import pandas as pd
|
||||
|
||||
@@ -14,7 +14,7 @@ import rowers
|
||||
from rowers import dataprep
|
||||
from rowers import tasks
|
||||
from rowers import c2stuff
|
||||
from rowers import stravastuff
|
||||
|
||||
import urllib
|
||||
import json
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import rowers
|
||||
from rowers import dataprep
|
||||
from rowers import tasks
|
||||
|
||||
from rowers import stravastuff
|
||||
from rowers import polarstuff
|
||||
import urllib
|
||||
import json
|
||||
@@ -26,6 +25,7 @@ from rowers.integrations import *
|
||||
|
||||
from django.db import transaction
|
||||
import rowers.garmin_stuff as gs
|
||||
import rowers.integrations.strava as strava
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(TESTING=True)
|
||||
@@ -1059,14 +1059,14 @@ class StravaObjects(DjangoTestCase):
|
||||
csvfilename=filename,uploadedtostrava=123,
|
||||
)
|
||||
|
||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
|
||||
def test_strava_webhook(self, mock_get, mock_post):
|
||||
url = reverse('strava_webhook_view')
|
||||
|
||||
params = {
|
||||
'hub.challenge':'aap',
|
||||
'hub.verify_token':stravastuff.webhookverification,
|
||||
'hub.verify_token':strava.webhookverification,
|
||||
}
|
||||
|
||||
url2 = url+'?'+urllib.parse.urlencode(params)
|
||||
@@ -1117,21 +1117,18 @@ class StravaObjects(DjangoTestCase):
|
||||
response = self.c.generic('POST', url, raw_data)
|
||||
self.assertEqual(response.status_code,200)
|
||||
|
||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.stravastuff.stravalib.Client',side_effect=MockStravalibClient)
|
||||
def test_workout_strava_upload(self, mock_get, mock_post,MockStravalibClient):
|
||||
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
|
||||
def test_workout_strava_upload(self, mock_get, mock_post):
|
||||
w = Workout.objects.get(id=1)
|
||||
res = stravastuff.workout_strava_upload(self.r.user,w,asynchron=True)
|
||||
self.assertEqual(res[1],-1)
|
||||
res = stravastuff.workout_strava_upload(self.r.user,w,asynchron=False)
|
||||
integration = StravaIntegration(self.r.user)
|
||||
res = integration.workout_export(w)
|
||||
|
||||
self.assertEqual(len(res[0]),43)
|
||||
self.assertEqual(res,1)
|
||||
|
||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.stravastuff.stravalib.Client',side_effect=MockStravalibClient)
|
||||
def test_strava_upload(self, mock_get, mock_post,MockStravalibClient):
|
||||
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
|
||||
def test_strava_upload(self, mock_get, mock_post):
|
||||
response = self.c.get('/rowers/workout/'+encoded1+'/stravauploadw/')
|
||||
|
||||
self.assertRedirects(response,
|
||||
@@ -1142,17 +1139,18 @@ class StravaObjects(DjangoTestCase):
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
|
||||
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
|
||||
def test_strava_list(self, mock_get, mockpost):
|
||||
result = rowers.stravastuff.rower_strava_token_refresh(self.u)
|
||||
integration = StravaIntegration(self.u)
|
||||
result = integration.token_refresh()
|
||||
self.assertEqual(result,"987654321234567898765432123456789")
|
||||
response = self.c.get('/rowers/workout/stravaimport/')
|
||||
|
||||
self.assertEqual(response.status_code,200)
|
||||
|
||||
@patch('rowers.utils.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.dataprep.getsmallrowdata_db')
|
||||
def test_strava_import(self, mock_get, mock_post,
|
||||
mocked_getsmallrowdata_db):
|
||||
@@ -1166,16 +1164,11 @@ class StravaObjects(DjangoTestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
||||
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
|
||||
def test_strava_callback(self, mock_post):
|
||||
response = self.c.get('/stravacall_back?code=absdef23&scope=read',follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
||||
def test_strava_token_refresh(self, mock_post):
|
||||
result = rowers.stravastuff.rower_strava_token_refresh(self.u)
|
||||
self.assertEqual(result,"987654321234567898765432123456789")
|
||||
|
||||
|
||||
|
||||
#@pytest.mark.django_db
|
||||
|
||||
@@ -424,7 +424,7 @@ class PermissionsViewTests(TestCase):
|
||||
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
|
||||
def test_permissions_coachee(
|
||||
self,view,permissions,
|
||||
mock_Session,
|
||||
mocked_session,
|
||||
mock_c2open,
|
||||
mocked_sqlalchemy,
|
||||
mocked_read_df_sql,
|
||||
|
||||
@@ -20,7 +20,7 @@ from rowers import dataprep
|
||||
from rowers import tasks
|
||||
from rowers import plannedsessions
|
||||
from rowers.views.workoutviews import get_video_id
|
||||
from rowers import stravastuff
|
||||
|
||||
|
||||
import rowingdata
|
||||
from rowers.c2stuff import getagegrouprecord
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
95,112,WorkoutDelete,delete workout,TRUE,403,basic,200,302,basic,403,403,coach,403,403,FALSE,FALSE,TRUE,FALSE,FALSE,
|
||||
96,113,workout_smoothenpace_view,smoothen pace,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
|
||||
97,114,workout_undo_smoothenpace_view,unsmoothen pace,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
|
||||
98,115,workout_c2import_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
98,115,workout_c2import_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,200,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
99,120,workout_stravaimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
|
||||
101,124,workout_getimportview,imports a workout from third party,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
103,126,workout_getstravaworkout_next,gets all strava workouts,TRUE,302,basic,302,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
|
||||
|
||||
|
@@ -3,7 +3,7 @@ from rowers.mytypes import workouttypes, boattypes, otwtypes, workoutsources, wo
|
||||
from rowers.rower_rules import is_promember
|
||||
import rowers.tpstuff as tpstuff
|
||||
import rowers.sporttracksstuff as sporttracksstuff
|
||||
import rowers.stravastuff as stravastuff
|
||||
|
||||
from rowers.integrations import *
|
||||
from rowers.utils import (
|
||||
geo_distance, serialize_list, deserialize_list, uniqify,
|
||||
@@ -213,10 +213,9 @@ def do_sync(w, options, quick=False):
|
||||
pass
|
||||
|
||||
if do_strava_export: # pragma: no cover
|
||||
strava_integration = StravaIntegration(w.user.user)
|
||||
try:
|
||||
message, id = stravastuff.workout_strava_upload(
|
||||
w.user.user, w, quick=quick, asynchron=True,
|
||||
)
|
||||
id = strava_integration.workout_export(w)
|
||||
dologging(
|
||||
'strava_export_log.log',
|
||||
'exporting workout {id} as {type}'.format(
|
||||
|
||||
@@ -664,8 +664,6 @@ urlpatterns = [
|
||||
re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/async/$',
|
||||
views.workout_getimportview, {'do_async': True}, name='workout_getimportview'),
|
||||
# re_path(r'^workout/stravaimport/all/$',views.workout_getstravaworkout_all,name='workout_getstravaworkout_all'),
|
||||
re_path(r'^workout/stravaimport/next/$', views.workout_getstravaworkout_next,
|
||||
name='workout_getstravaworkout_next'),
|
||||
re_path(r'^workout/sporttracksimport/$', views.workout_sporttracksimport_view,
|
||||
name='workout_sporttracksimport_view'),
|
||||
re_path(r'^workout/sporttracksimport/user/(?P<userid>\d+)/$',
|
||||
|
||||
@@ -7,7 +7,9 @@ from rowers.views.statements import *
|
||||
from rowers.plannedsessions import get_dates_timeperiod
|
||||
from rowers.tasks import fetch_strava_workout
|
||||
|
||||
from rowers.integrations.c2 import C2Integration
|
||||
from rowers.integrations import C2Integration, StravaIntegration
|
||||
|
||||
import rowers.integrations.strava as strava
|
||||
|
||||
import numpy
|
||||
|
||||
@@ -82,8 +84,12 @@ def workout_strava_upload_view(request, id=0):
|
||||
r = getrower(request.user)
|
||||
w = get_workout_by_opaqueid(request, id)
|
||||
result = -1
|
||||
comment, result = stravastuff.workout_strava_upload(
|
||||
r.user, w, asynchron=True)
|
||||
strava_integration = StravaIntegration(request.user)
|
||||
try:
|
||||
stravaid = strava_integration.workout_export(w)
|
||||
except NoTokenError:
|
||||
return HttpResponseRedirect("/rowers/me/stravaauthorize")
|
||||
|
||||
messages.info(
|
||||
request, 'Your workout will be synchronized to Strava in the background')
|
||||
|
||||
@@ -210,21 +216,12 @@ def rower_garmin_authorize(request): # pragma: no cover
|
||||
|
||||
@login_required()
|
||||
def rower_strava_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": STRAVA_CLIENT_ID,
|
||||
"response_type": "code",
|
||||
"redirect_uri": STRAVA_REDIRECT_URI,
|
||||
"scope": "activity:write,activity:read_all"}
|
||||
|
||||
url = "https://www.strava.com/oauth/authorize?" + \
|
||||
urllib.parse.urlencode(params)
|
||||
strava_integration = StravaIntegration(request.user)
|
||||
url = strava_integration.make_authorization_url()
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# Polar Authorization
|
||||
|
||||
|
||||
@@ -673,7 +670,7 @@ def workout_nkimport_view(request, userid=0, after=0, before=0):
|
||||
if (res.status_code != 200): # pragma: no cover
|
||||
if (res.status_code == 401):
|
||||
r = getrower(request.user)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None):
|
||||
if (r.nktoken == '') or (r.nktoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return HttpResponseRedirect("/rowers/me/nkauthorize/")
|
||||
message = "Something went wrong in workout_nkimport_view"
|
||||
@@ -786,6 +783,7 @@ def workout_nkimport_view(request, userid=0, after=0, before=0):
|
||||
|
||||
@login_required()
|
||||
def rower_process_stravacallback(request):
|
||||
strava_integration = StravaIntegration(request.user)
|
||||
try:
|
||||
code = request.GET['code']
|
||||
_ = request.GET['scope']
|
||||
@@ -800,7 +798,7 @@ def rower_process_stravacallback(request):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
res = stravastuff.get_token(code)
|
||||
res = strava_integration.get_token(code)
|
||||
|
||||
if res[0]:
|
||||
access_token = res[0]
|
||||
@@ -815,7 +813,7 @@ def rower_process_stravacallback(request):
|
||||
r.stravarefreshtoken = refresh_token
|
||||
|
||||
r.save()
|
||||
_ = stravastuff.set_strava_athlete_id(r.user)
|
||||
_ = strava_integration.set_strava_athlete_id()
|
||||
|
||||
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
|
||||
messages.info(request, successmessage)
|
||||
@@ -1169,7 +1167,7 @@ def workout_rojaboimport_view(request, message="", userid=0):
|
||||
},
|
||||
{
|
||||
'url': reverse('workout_rojaboimport_view'),
|
||||
'name': 'Strava'
|
||||
'name': 'Rojabo'
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1197,130 +1195,69 @@ def workout_stravaimport_view(request, message="", userid=0):
|
||||
url = reverse('workout_stravaimport_view',
|
||||
kwargs={'userid': request.user.id})
|
||||
return HttpResponseRedirect(url)
|
||||
# if r.user != request.user:
|
||||
# messages.info(request,"You cannot import other people's workouts from Strava")
|
||||
|
||||
strava_integration = StravaIntegration(request.user)
|
||||
try:
|
||||
_ = strava_open(request.user)
|
||||
_ = strava_integration.open()
|
||||
except NoTokenError: # pragma: no cover
|
||||
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
|
||||
|
||||
res = stravastuff.get_strava_workout_list(request.user)
|
||||
workouts = strava_integration.get_workout_list()
|
||||
|
||||
if (res.status_code != 200): # pragma: no cover
|
||||
if (res.status_code == 401):
|
||||
r = getrower(request.user)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
|
||||
message = "Something went wrong in workout_stravaimport_view"
|
||||
messages.error(request, message)
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
workouts = []
|
||||
r = getrower(request.user)
|
||||
rower = r
|
||||
stravaids = [int(item['id']) for item in res.json()]
|
||||
stravadata = [{
|
||||
'id': int(item['id']),
|
||||
'elapsed_time':item['elapsed_time'],
|
||||
'start_date':item['start_date'],
|
||||
} for item in res.json()]
|
||||
if request.method == "POST":
|
||||
try: # pragma: no cover
|
||||
tdict = dict(request.POST.lists())
|
||||
ids = tdict['workoutid']
|
||||
stravaids = [int(id) for id in ids]
|
||||
alldata = {}
|
||||
|
||||
wfailed = Workout.objects.filter(user=r, uploadedtostrava=-1)
|
||||
for stravaid in stravaids:
|
||||
csvfilename = 'media/{code}_{stravaid}.csv'.format(
|
||||
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
|
||||
messages.info(request,
|
||||
'Your Strava workouts will be imported in the background.'
|
||||
' It may take a few minutes before they appear.')
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
except KeyError: # pragma: no cover
|
||||
pass
|
||||
|
||||
for w in wfailed: # pragma: no cover
|
||||
for item in stravadata:
|
||||
elapsed_time = item['elapsed_time']
|
||||
start_date = item['start_date']
|
||||
stravaid = item['id']
|
||||
if arrow.get(start_date) == arrow.get(w.startdatetime):
|
||||
elapsed_td = datetime.timedelta(seconds=int(elapsed_time))
|
||||
elapsed_time = datetime.datetime.strptime(
|
||||
str(elapsed_td),
|
||||
"%H:%M:%S"
|
||||
)
|
||||
if str(elapsed_time)[-7:] == str(w.duration)[-7:]:
|
||||
w.uploadedtostrava = int(stravaid)
|
||||
w.save()
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url': '/rowers/list-workouts/',
|
||||
'name': 'Workouts'
|
||||
},
|
||||
{
|
||||
'url': reverse('workout_stravaimport_view'),
|
||||
'name': 'Strava'
|
||||
},
|
||||
]
|
||||
|
||||
knownstravaids = uniqify([
|
||||
w.uploadedtostrava for w in Workout.objects.filter(user=r)
|
||||
])
|
||||
checknew = request.GET.get('selectallnew', False)
|
||||
|
||||
# 2022-10-24 sorting the results
|
||||
workouts = sorted(workouts, key = lambda d:d['starttime'], reverse=True)
|
||||
|
||||
return render(request, 'list_import.html',
|
||||
{'workouts': workouts,
|
||||
'rower': r,
|
||||
'active': 'nav-workouts',
|
||||
'breadcrumbs': breadcrumbs,
|
||||
'teams': get_my_teams(request.user),
|
||||
'checknew': checknew,
|
||||
'integration': 'Strava'
|
||||
})
|
||||
|
||||
for item in res.json():
|
||||
d = int(float(item['distance']))
|
||||
i = item['id']
|
||||
if i in knownstravaids: # pragma: no cover
|
||||
nnn = ''
|
||||
else:
|
||||
nnn = 'NEW'
|
||||
n = item['name']
|
||||
ttot = str(datetime.timedelta(
|
||||
seconds=int(float(item['elapsed_time']))))
|
||||
s = item['start_date']
|
||||
r = item['type']
|
||||
keys = ['id', 'distance', 'duration',
|
||||
'starttime', 'type', 'name', 'new']
|
||||
values = [i, d, ttot, s, r, n, nnn]
|
||||
res2 = dict(zip(keys, values))
|
||||
workouts.append(res2)
|
||||
|
||||
if request.method == "POST":
|
||||
try: # pragma: no cover
|
||||
tdict = dict(request.POST.lists())
|
||||
ids = tdict['workoutid']
|
||||
stravaids = [int(id) for id in ids]
|
||||
alldata = {}
|
||||
for item in res.json():
|
||||
alldata[item['id']] = item
|
||||
for stravaid in stravaids:
|
||||
csvfilename = 'media/{code}_{stravaid}.csv'.format(
|
||||
code=uuid4().hex[:16], stravaid=stravaid)
|
||||
_ = myqueue(
|
||||
queue,
|
||||
fetch_strava_workout,
|
||||
rower.stravatoken,
|
||||
stravastuff.oauth_data,
|
||||
stravaid,
|
||||
csvfilename,
|
||||
rower.user.id
|
||||
)
|
||||
# done, redirect to workouts list
|
||||
messages.info(request,
|
||||
'Your Strava workouts will be imported in the background.'
|
||||
' It may take a few minutes before they appear.')
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
except KeyError: # pragma: no cover
|
||||
pass
|
||||
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url': '/rowers/list-workouts/',
|
||||
'name': 'Workouts'
|
||||
},
|
||||
{
|
||||
'url': reverse('workout_stravaimport_view'),
|
||||
'name': 'Strava'
|
||||
},
|
||||
]
|
||||
|
||||
checknew = request.GET.get('selectallnew', False)
|
||||
|
||||
# 2022-10-24 sorting the results
|
||||
workouts = sorted(workouts, key = lambda d:d['starttime'], reverse=True)
|
||||
|
||||
return render(request, 'strava_list_import.html',
|
||||
{'workouts': workouts,
|
||||
'rower': rower,
|
||||
'active': 'nav-workouts',
|
||||
'breadcrumbs': breadcrumbs,
|
||||
'teams': get_my_teams(request.user),
|
||||
'checknew': checknew,
|
||||
})
|
||||
|
||||
return HttpResponse(res) # pragma: no cover
|
||||
|
||||
# for Strava webhook request validation
|
||||
|
||||
@@ -1330,7 +1267,7 @@ def strava_webhook_view(request):
|
||||
if request.method == 'GET':
|
||||
challenge = request.GET.get('hub.challenge')
|
||||
verificationtoken = request.GET.get('hub.verify_token')
|
||||
if verificationtoken != stravastuff.webhookverification: # pragma: no cover
|
||||
if verificationtoken != strava.webhookverification: # pragma: no cover
|
||||
return HttpResponse(status=403)
|
||||
data = {"hub.challenge": challenge}
|
||||
return JSONResponse(data)
|
||||
@@ -1374,8 +1311,9 @@ def strava_webhook_view(request):
|
||||
|
||||
ws = Workout.objects.filter(uploadedtostrava=stravaid)
|
||||
if ws.count() == 0 and r.strava_auto_import:
|
||||
job = stravastuff.async_get_workout(r.user, stravaid)
|
||||
if job == 0: # pragma: no cover
|
||||
strava_integration = StravaIntegration(r.user)
|
||||
jobid = strava_integration.get_workout(stravaid)
|
||||
if jobid == 0: # pragma: no cover
|
||||
dologging('strava_webhooks.log',
|
||||
'Strava strava_open yielded NoTokenError')
|
||||
else: # pragma: no cover
|
||||
@@ -1728,7 +1666,7 @@ def workout_c2import_view(request, page=1, userid=0, message=""):
|
||||
|
||||
workouts = c2_integration.get_workout_list(page=1)
|
||||
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
try: # pragma: no cover
|
||||
tdict = dict(request.POST.lists())
|
||||
@@ -1801,7 +1739,7 @@ importauthorizeviews = {
|
||||
|
||||
importsources = {
|
||||
'c2': C2Integration,
|
||||
'strava': stravastuff,
|
||||
'strava': StravaIntegration,
|
||||
'polar': polarstuff,
|
||||
'ownapi': ownapistuff,
|
||||
'sporttracks': sporttracksstuff,
|
||||
@@ -1960,24 +1898,3 @@ def workout_getsporttracksworkout_all(request):
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# Imports all new workouts from SportTracks
|
||||
@login_required()
|
||||
def workout_getstravaworkout_next(request): # pragma: no cover
|
||||
|
||||
r = Rower.objects.get(user=request.user)
|
||||
|
||||
res = stravastuff.get_strava_workout_list(r.user)
|
||||
|
||||
if (res.status_code != 200):
|
||||
return 0
|
||||
else:
|
||||
alldata = {}
|
||||
for item in res.json():
|
||||
alldata[item['id']] = item
|
||||
|
||||
_ = stravastuff.create_async_workout(
|
||||
alldata, r.user, stravaid, debug=True)
|
||||
|
||||
url = reverse('workouts_view')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@@ -199,10 +199,10 @@ from rowers.rp3stuff import rp3_open
|
||||
from rowers.sporttracksstuff import sporttracks_open
|
||||
from rowers.tpstuff import tp_open
|
||||
from iso8601 import ParseError
|
||||
import rowers.stravastuff as stravastuff
|
||||
|
||||
import rowers.rojabo_stuff as rojabo_stuff
|
||||
import rowers.garmin_stuff as garmin_stuff
|
||||
from rowers.stravastuff import strava_open
|
||||
|
||||
from rowers.rojabo_stuff import rojabo_open
|
||||
import rowers.polarstuff as polarstuff
|
||||
import rowers.sporttracksstuff as sporttracksstuff
|
||||
|
||||
@@ -2566,7 +2566,7 @@ def workout_smoothenpace_view(request, id=0, message="", successmessage=""):
|
||||
if 'originalvelo' not in row.df:
|
||||
row.df['originalvelo'] = velo
|
||||
|
||||
velo2 = stravastuff.ewmovingaverage(velo, 5)
|
||||
velo2 = utils.ewmovingaverage(velo, 5)
|
||||
|
||||
pace2 = 500./abs(velo2)
|
||||
|
||||
@@ -5641,10 +5641,9 @@ def workout_upload_view(request,
|
||||
messages.error(request, message)
|
||||
|
||||
if (upload_to_strava): # pragma: no cover
|
||||
strava_integration = StravaIntegration(request.user)
|
||||
try:
|
||||
message, id = stravastuff.workout_strava_upload(
|
||||
request.user, w,
|
||||
)
|
||||
id = strava_integration.workout_export(w)
|
||||
except NoTokenError:
|
||||
id = 0
|
||||
message = "Please connect to Strava first"
|
||||
|
||||
Reference in New Issue
Block a user