adding strava
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
from .c2 import C2Integration
|
from .c2 import C2Integration
|
||||||
|
from .strava import StravaIntegration
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import numpy as np
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import urllib
|
import urllib
|
||||||
from rowers.utils import dologging, uniqify
|
from rowers.utils import dologging, uniqify, custom_exception_handler
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import requests
|
import requests
|
||||||
from pytz.exceptions import UnknownTimeZoneError
|
from pytz.exceptions import UnknownTimeZoneError
|
||||||
@@ -432,7 +432,7 @@ class C2Integration(SyncIntegration):
|
|||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
nnn = 'NEW'
|
nnn = 'NEW'
|
||||||
keys = ['id', 'distance', 'duration', 'starttime',
|
keys = ['id', 'distance', 'duration', 'starttime',
|
||||||
'rowtype', 'source', 'comment', 'new']
|
'rowtype', 'source', 'name', 'new']
|
||||||
values = [i, d, ttot, s, r, s2, c, nnn]
|
values = [i, d, ttot, s, r, s2, c, nnn]
|
||||||
ress = dict(zip(keys, values))
|
ress = dict(zip(keys, values))
|
||||||
workouts.append(ress)
|
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
|
@abstractmethod
|
||||||
def createworkoutdata(w, *args, **kwargs) -> dict:
|
def createworkoutdata(w, *args, **kwargs):
|
||||||
return {}
|
return None
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def workout_export(workout, *args, **kwargs) -> str:
|
def workout_export(workout, *args, **kwargs) -> str:
|
||||||
@@ -51,12 +51,12 @@ class SyncIntegration(metaclass=ABCMeta):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_workout(id) -> int:
|
def get_workout(id) -> int:
|
||||||
pass
|
return 0
|
||||||
|
|
||||||
# need to unify workout list
|
# need to unify workout list
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_workout_list(*args, **kwargs) -> list:
|
def get_workout_list(*args, **kwargs) -> list:
|
||||||
pass
|
return []
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover
|
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.metrics as metrics
|
||||||
import rowers.dataprep as dataprep
|
import rowers.dataprep as dataprep
|
||||||
from rowers.dataprep import rdata
|
from rowers.dataprep import rdata
|
||||||
import rowers.stravastuff as stravastuff
|
import rowers.utils as utils
|
||||||
|
|
||||||
from scipy.interpolate import griddata
|
from scipy.interpolate import griddata
|
||||||
from scipy.signal import savgol_filter
|
from scipy.signal import savgol_filter
|
||||||
from scipy import optimize
|
from scipy import optimize
|
||||||
@@ -5401,7 +5402,7 @@ def interactive_flexchart_stacked(id, r, xparam='time',
|
|||||||
if metricsdicts[column]['maysmooth']:
|
if metricsdicts[column]['maysmooth']:
|
||||||
nrsteps = int(log2(r.usersmooth))
|
nrsteps = int(log2(r.usersmooth))
|
||||||
for i in range(nrsteps):
|
for i in range(nrsteps):
|
||||||
rowdata[column] = stravastuff.ewmovingaverage(
|
rowdata[column] = utils.ewmovingaverage(
|
||||||
rowdata[column], 5)
|
rowdata[column], 5)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
@@ -5767,7 +5768,7 @@ def interactive_flex_chart2(id, r, promember=0,
|
|||||||
if metricsdicts[column]['maysmooth']:
|
if metricsdicts[column]['maysmooth']:
|
||||||
nrsteps = int(log2(r.usersmooth))
|
nrsteps = int(log2(r.usersmooth))
|
||||||
for i in range(nrsteps):
|
for i in range(nrsteps):
|
||||||
rowdata[column] = stravastuff.ewmovingaverage(
|
rowdata[column] = utils.ewmovingaverage(
|
||||||
rowdata[column], 5)
|
rowdata[column], 5)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import rowers.uploads as uploads
|
|||||||
import rowers.polarstuff as polarstuff
|
import rowers.polarstuff as polarstuff
|
||||||
|
|
||||||
import rowers.rp3stuff as rp3stuff
|
import rowers.rp3stuff as rp3stuff
|
||||||
import rowers.stravastuff as stravastuff
|
|
||||||
import rowers.nkstuff as nkstuff
|
import rowers.nkstuff as nkstuff
|
||||||
from rowers.opaque import encoder
|
from rowers.opaque import encoder
|
||||||
from rowers.integrations import *
|
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,6 +9,7 @@
|
|||||||
|
|
||||||
<ul class="main-content">
|
<ul class="main-content">
|
||||||
{% if workouts %}
|
{% if workouts %}
|
||||||
|
{% if integration == 'C2 Logbook' %}
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
<a href="/rowers/workout/c2import/all/{{ page }}">Import all NEW</a>
|
<a href="/rowers/workout/c2import/all/{{ page }}">Import all NEW</a>
|
||||||
<p>This imports all workouts that have not been imported to rowsandall.com.
|
<p>This imports all workouts that have not been imported to rowsandall.com.
|
||||||
@@ -29,11 +30,12 @@
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li class="grid_4">
|
<li class="grid_4">
|
||||||
<form enctype="multipart/form-data" method="post">
|
<form enctype="multipart/form-data" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input name='workouts' type="submit" value="Import selected workouts">
|
<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">
|
<table width="70%" class="listtable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -43,7 +45,7 @@
|
|||||||
<th> Total Distance</th>
|
<th> Total Distance</th>
|
||||||
<th> Type</th>
|
<th> Type</th>
|
||||||
<th> Source</th>
|
<th> Source</th>
|
||||||
<th> Comment</th>
|
<th> Name</th>
|
||||||
<th> New</th>
|
<th> New</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -62,7 +64,7 @@
|
|||||||
<td>{{ workout|lookup:'distance' }}</td>
|
<td>{{ workout|lookup:'distance' }}</td>
|
||||||
<td>{{ workout|lookup:'rowtype' }}</td>
|
<td>{{ workout|lookup:'rowtype' }}</td>
|
||||||
<td>{{ workout|lookup:'source' }}</td>
|
<td>{{ workout|lookup:'source' }}</td>
|
||||||
<td>{{ workout|lookup:'comment' }}</td>
|
<td>{{ workout|lookup:'name' }}</td>
|
||||||
<td>
|
<td>
|
||||||
{{ workout|lookup:'new' }}
|
{{ workout|lookup:'new' }}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ def deltatimeprint(d): # pragma: no cover
|
|||||||
def c2userid(user): # pragma: no cover
|
def c2userid(user): # pragma: no cover
|
||||||
c2integration = C2Integration(user)
|
c2integration = C2Integration(user)
|
||||||
|
|
||||||
c2userid = c2integration.get_userid(thetoken)
|
c2userid = c2integration.get_userid(user)
|
||||||
|
|
||||||
return c2userid
|
return c2userid
|
||||||
|
|
||||||
@@ -566,6 +566,8 @@ def lookup(dict, key):
|
|||||||
s = dict.get(key)
|
s = dict.get(key)
|
||||||
except KeyError: # pragma: no cover
|
except KeyError: # pragma: no cover
|
||||||
return None
|
return None
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
if isinstance(s, string_types) and len(s) > 22:
|
if isinstance(s, string_types) and len(s) > 22:
|
||||||
s = s[:22]
|
s = s[:22]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import rowers
|
|||||||
from rowers import dataprep
|
from rowers import dataprep
|
||||||
from rowers import tasks
|
from rowers import tasks
|
||||||
from rowers import c2stuff
|
from rowers import c2stuff
|
||||||
from rowers import stravastuff
|
|
||||||
import urllib
|
import urllib
|
||||||
import json
|
import json
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import rowers
|
|||||||
from rowers import dataprep
|
from rowers import dataprep
|
||||||
from rowers import tasks
|
from rowers import tasks
|
||||||
from rowers import c2stuff
|
from rowers import c2stuff
|
||||||
from rowers import stravastuff
|
|
||||||
import urllib
|
import urllib
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import rowers
|
|||||||
from rowers import dataprep
|
from rowers import dataprep
|
||||||
from rowers import tasks
|
from rowers import tasks
|
||||||
|
|
||||||
from rowers import stravastuff
|
|
||||||
from rowers import polarstuff
|
from rowers import polarstuff
|
||||||
import urllib
|
import urllib
|
||||||
import json
|
import json
|
||||||
@@ -26,6 +25,7 @@ from rowers.integrations import *
|
|||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
import rowers.garmin_stuff as gs
|
import rowers.garmin_stuff as gs
|
||||||
|
import rowers.integrations.strava as strava
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@override_settings(TESTING=True)
|
@override_settings(TESTING=True)
|
||||||
@@ -1059,14 +1059,14 @@ class StravaObjects(DjangoTestCase):
|
|||||||
csvfilename=filename,uploadedtostrava=123,
|
csvfilename=filename,uploadedtostrava=123,
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
|
||||||
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
|
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
|
||||||
def test_strava_webhook(self, mock_get, mock_post):
|
def test_strava_webhook(self, mock_get, mock_post):
|
||||||
url = reverse('strava_webhook_view')
|
url = reverse('strava_webhook_view')
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'hub.challenge':'aap',
|
'hub.challenge':'aap',
|
||||||
'hub.verify_token':stravastuff.webhookverification,
|
'hub.verify_token':strava.webhookverification,
|
||||||
}
|
}
|
||||||
|
|
||||||
url2 = url+'?'+urllib.parse.urlencode(params)
|
url2 = url+'?'+urllib.parse.urlencode(params)
|
||||||
@@ -1117,21 +1117,18 @@ class StravaObjects(DjangoTestCase):
|
|||||||
response = self.c.generic('POST', url, raw_data)
|
response = self.c.generic('POST', url, raw_data)
|
||||||
self.assertEqual(response.status_code,200)
|
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)
|
||||||
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
|
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
|
||||||
@patch('rowers.stravastuff.stravalib.Client',side_effect=MockStravalibClient)
|
def test_workout_strava_upload(self, mock_get, mock_post):
|
||||||
def test_workout_strava_upload(self, mock_get, mock_post,MockStravalibClient):
|
|
||||||
w = Workout.objects.get(id=1)
|
w = Workout.objects.get(id=1)
|
||||||
res = stravastuff.workout_strava_upload(self.r.user,w,asynchron=True)
|
integration = StravaIntegration(self.r.user)
|
||||||
self.assertEqual(res[1],-1)
|
res = integration.workout_export(w)
|
||||||
res = stravastuff.workout_strava_upload(self.r.user,w,asynchron=False)
|
|
||||||
|
|
||||||
self.assertEqual(len(res[0]),43)
|
self.assertEqual(res,1)
|
||||||
|
|
||||||
@patch('rowers.stravastuff.requests.post', side_effect=mocked_requests)
|
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
|
||||||
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
|
@patch('rowers.integrations.strava.requests.get', side_effect=mocked_requests)
|
||||||
@patch('rowers.stravastuff.stravalib.Client',side_effect=MockStravalibClient)
|
def test_strava_upload(self, mock_get, mock_post):
|
||||||
def test_strava_upload(self, mock_get, mock_post,MockStravalibClient):
|
|
||||||
response = self.c.get('/rowers/workout/'+encoded1+'/stravauploadw/')
|
response = self.c.get('/rowers/workout/'+encoded1+'/stravauploadw/')
|
||||||
|
|
||||||
self.assertRedirects(response,
|
self.assertRedirects(response,
|
||||||
@@ -1142,17 +1139,18 @@ class StravaObjects(DjangoTestCase):
|
|||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
|
||||||
@patch('rowers.stravastuff.requests.get', side_effect=mocked_requests)
|
@patch('rowers.integrations.strava.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)
|
||||||
def test_strava_list(self, mock_get, mockpost):
|
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")
|
self.assertEqual(result,"987654321234567898765432123456789")
|
||||||
response = self.c.get('/rowers/workout/stravaimport/')
|
response = self.c.get('/rowers/workout/stravaimport/')
|
||||||
|
|
||||||
self.assertEqual(response.status_code,200)
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
@patch('rowers.utils.requests.get', side_effect=mocked_requests)
|
@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')
|
@patch('rowers.dataprep.getsmallrowdata_db')
|
||||||
def test_strava_import(self, mock_get, mock_post,
|
def test_strava_import(self, mock_get, mock_post,
|
||||||
mocked_getsmallrowdata_db):
|
mocked_getsmallrowdata_db):
|
||||||
@@ -1166,16 +1164,11 @@ class StravaObjects(DjangoTestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
def test_strava_callback(self, mock_post):
|
||||||
response = self.c.get('/stravacall_back?code=absdef23&scope=read',follow=True)
|
response = self.c.get('/stravacall_back?code=absdef23&scope=read',follow=True)
|
||||||
self.assertEqual(response.status_code, 200)
|
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
|
#@pytest.mark.django_db
|
||||||
|
|||||||
@@ -424,7 +424,7 @@ class PermissionsViewTests(TestCase):
|
|||||||
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
|
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
|
||||||
def test_permissions_coachee(
|
def test_permissions_coachee(
|
||||||
self,view,permissions,
|
self,view,permissions,
|
||||||
mock_Session,
|
mocked_session,
|
||||||
mock_c2open,
|
mock_c2open,
|
||||||
mocked_sqlalchemy,
|
mocked_sqlalchemy,
|
||||||
mocked_read_df_sql,
|
mocked_read_df_sql,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from rowers import dataprep
|
|||||||
from rowers import tasks
|
from rowers import tasks
|
||||||
from rowers import plannedsessions
|
from rowers import plannedsessions
|
||||||
from rowers.views.workoutviews import get_video_id
|
from rowers.views.workoutviews import get_video_id
|
||||||
from rowers import stravastuff
|
|
||||||
|
|
||||||
import rowingdata
|
import rowingdata
|
||||||
from rowers.c2stuff import getagegrouprecord
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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
|
from rowers.rower_rules import is_promember
|
||||||
import rowers.tpstuff as tpstuff
|
import rowers.tpstuff as tpstuff
|
||||||
import rowers.sporttracksstuff as sporttracksstuff
|
import rowers.sporttracksstuff as sporttracksstuff
|
||||||
import rowers.stravastuff as stravastuff
|
|
||||||
from rowers.integrations import *
|
from rowers.integrations import *
|
||||||
from rowers.utils import (
|
from rowers.utils import (
|
||||||
geo_distance, serialize_list, deserialize_list, uniqify,
|
geo_distance, serialize_list, deserialize_list, uniqify,
|
||||||
@@ -213,10 +213,9 @@ def do_sync(w, options, quick=False):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if do_strava_export: # pragma: no cover
|
if do_strava_export: # pragma: no cover
|
||||||
|
strava_integration = StravaIntegration(w.user.user)
|
||||||
try:
|
try:
|
||||||
message, id = stravastuff.workout_strava_upload(
|
id = strava_integration.workout_export(w)
|
||||||
w.user.user, w, quick=quick, asynchron=True,
|
|
||||||
)
|
|
||||||
dologging(
|
dologging(
|
||||||
'strava_export_log.log',
|
'strava_export_log.log',
|
||||||
'exporting workout {id} as {type}'.format(
|
'exporting workout {id} as {type}'.format(
|
||||||
|
|||||||
@@ -664,8 +664,6 @@ urlpatterns = [
|
|||||||
re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/async/$',
|
re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/async/$',
|
||||||
views.workout_getimportview, {'do_async': True}, name='workout_getimportview'),
|
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/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,
|
re_path(r'^workout/sporttracksimport/$', views.workout_sporttracksimport_view,
|
||||||
name='workout_sporttracksimport_view'),
|
name='workout_sporttracksimport_view'),
|
||||||
re_path(r'^workout/sporttracksimport/user/(?P<userid>\d+)/$',
|
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.plannedsessions import get_dates_timeperiod
|
||||||
from rowers.tasks import fetch_strava_workout
|
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
|
import numpy
|
||||||
|
|
||||||
@@ -82,8 +84,12 @@ def workout_strava_upload_view(request, id=0):
|
|||||||
r = getrower(request.user)
|
r = getrower(request.user)
|
||||||
w = get_workout_by_opaqueid(request, id)
|
w = get_workout_by_opaqueid(request, id)
|
||||||
result = -1
|
result = -1
|
||||||
comment, result = stravastuff.workout_strava_upload(
|
strava_integration = StravaIntegration(request.user)
|
||||||
r.user, w, asynchron=True)
|
try:
|
||||||
|
stravaid = strava_integration.workout_export(w)
|
||||||
|
except NoTokenError:
|
||||||
|
return HttpResponseRedirect("/rowers/me/stravaauthorize")
|
||||||
|
|
||||||
messages.info(
|
messages.info(
|
||||||
request, 'Your workout will be synchronized to Strava in the background')
|
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()
|
@login_required()
|
||||||
def rower_strava_authorize(request): # pragma: no cover
|
def rower_strava_authorize(request): # pragma: no cover
|
||||||
# Generate a random string for the state parameter
|
strava_integration = StravaIntegration(request.user)
|
||||||
# Save it for use later to prevent xsrf attacks
|
url = strava_integration.make_authorization_url()
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
# Polar Authorization
|
# 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 != 200): # pragma: no cover
|
||||||
if (res.status_code == 401):
|
if (res.status_code == 401):
|
||||||
r = getrower(request.user)
|
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"
|
s = "Token doesn't exist. Need to authorize"
|
||||||
return HttpResponseRedirect("/rowers/me/nkauthorize/")
|
return HttpResponseRedirect("/rowers/me/nkauthorize/")
|
||||||
message = "Something went wrong in workout_nkimport_view"
|
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()
|
@login_required()
|
||||||
def rower_process_stravacallback(request):
|
def rower_process_stravacallback(request):
|
||||||
|
strava_integration = StravaIntegration(request.user)
|
||||||
try:
|
try:
|
||||||
code = request.GET['code']
|
code = request.GET['code']
|
||||||
_ = request.GET['scope']
|
_ = request.GET['scope']
|
||||||
@@ -800,7 +798,7 @@ def rower_process_stravacallback(request):
|
|||||||
|
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
res = stravastuff.get_token(code)
|
res = strava_integration.get_token(code)
|
||||||
|
|
||||||
if res[0]:
|
if res[0]:
|
||||||
access_token = res[0]
|
access_token = res[0]
|
||||||
@@ -815,7 +813,7 @@ def rower_process_stravacallback(request):
|
|||||||
r.stravarefreshtoken = refresh_token
|
r.stravarefreshtoken = refresh_token
|
||||||
|
|
||||||
r.save()
|
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"
|
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
|
||||||
messages.info(request, successmessage)
|
messages.info(request, successmessage)
|
||||||
@@ -1169,7 +1167,7 @@ def workout_rojaboimport_view(request, message="", userid=0):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': reverse('workout_rojaboimport_view'),
|
'url': reverse('workout_rojaboimport_view'),
|
||||||
'name': 'Strava'
|
'name': 'Rojabo'
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1197,74 +1195,14 @@ def workout_stravaimport_view(request, message="", userid=0):
|
|||||||
url = reverse('workout_stravaimport_view',
|
url = reverse('workout_stravaimport_view',
|
||||||
kwargs={'userid': request.user.id})
|
kwargs={'userid': request.user.id})
|
||||||
return HttpResponseRedirect(url)
|
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:
|
try:
|
||||||
_ = strava_open(request.user)
|
_ = strava_integration.open()
|
||||||
except NoTokenError: # pragma: no cover
|
except NoTokenError: # pragma: no cover
|
||||||
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
|
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()]
|
|
||||||
|
|
||||||
wfailed = Workout.objects.filter(user=r, 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=r)
|
|
||||||
])
|
|
||||||
|
|
||||||
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":
|
if request.method == "POST":
|
||||||
try: # pragma: no cover
|
try: # pragma: no cover
|
||||||
@@ -1272,19 +1210,18 @@ def workout_stravaimport_view(request, message="", userid=0):
|
|||||||
ids = tdict['workoutid']
|
ids = tdict['workoutid']
|
||||||
stravaids = [int(id) for id in ids]
|
stravaids = [int(id) for id in ids]
|
||||||
alldata = {}
|
alldata = {}
|
||||||
for item in res.json():
|
|
||||||
alldata[item['id']] = item
|
|
||||||
for stravaid in stravaids:
|
for stravaid in stravaids:
|
||||||
csvfilename = 'media/{code}_{stravaid}.csv'.format(
|
csvfilename = 'media/{code}_{stravaid}.csv'.format(
|
||||||
code=uuid4().hex[:16], stravaid=stravaid)
|
code=uuid4().hex[:16], stravaid=stravaid)
|
||||||
_ = myqueue(
|
_ = myqueue(
|
||||||
queue,
|
queue,
|
||||||
fetch_strava_workout,
|
fetch_strava_workout,
|
||||||
rower.stravatoken,
|
r.stravatoken,
|
||||||
stravastuff.oauth_data,
|
strava_integration.oauth_data,
|
||||||
stravaid,
|
stravaid,
|
||||||
csvfilename,
|
csvfilename,
|
||||||
rower.user.id
|
r.user.id
|
||||||
)
|
)
|
||||||
# done, redirect to workouts list
|
# done, redirect to workouts list
|
||||||
messages.info(request,
|
messages.info(request,
|
||||||
@@ -1311,16 +1248,16 @@ def workout_stravaimport_view(request, message="", userid=0):
|
|||||||
# 2022-10-24 sorting the results
|
# 2022-10-24 sorting the results
|
||||||
workouts = sorted(workouts, key = lambda d:d['starttime'], reverse=True)
|
workouts = sorted(workouts, key = lambda d:d['starttime'], reverse=True)
|
||||||
|
|
||||||
return render(request, 'strava_list_import.html',
|
return render(request, 'list_import.html',
|
||||||
{'workouts': workouts,
|
{'workouts': workouts,
|
||||||
'rower': rower,
|
'rower': r,
|
||||||
'active': 'nav-workouts',
|
'active': 'nav-workouts',
|
||||||
'breadcrumbs': breadcrumbs,
|
'breadcrumbs': breadcrumbs,
|
||||||
'teams': get_my_teams(request.user),
|
'teams': get_my_teams(request.user),
|
||||||
'checknew': checknew,
|
'checknew': checknew,
|
||||||
|
'integration': 'Strava'
|
||||||
})
|
})
|
||||||
|
|
||||||
return HttpResponse(res) # pragma: no cover
|
|
||||||
|
|
||||||
# for Strava webhook request validation
|
# for Strava webhook request validation
|
||||||
|
|
||||||
@@ -1330,7 +1267,7 @@ def strava_webhook_view(request):
|
|||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
challenge = request.GET.get('hub.challenge')
|
challenge = request.GET.get('hub.challenge')
|
||||||
verificationtoken = request.GET.get('hub.verify_token')
|
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)
|
return HttpResponse(status=403)
|
||||||
data = {"hub.challenge": challenge}
|
data = {"hub.challenge": challenge}
|
||||||
return JSONResponse(data)
|
return JSONResponse(data)
|
||||||
@@ -1374,8 +1311,9 @@ def strava_webhook_view(request):
|
|||||||
|
|
||||||
ws = Workout.objects.filter(uploadedtostrava=stravaid)
|
ws = Workout.objects.filter(uploadedtostrava=stravaid)
|
||||||
if ws.count() == 0 and r.strava_auto_import:
|
if ws.count() == 0 and r.strava_auto_import:
|
||||||
job = stravastuff.async_get_workout(r.user, stravaid)
|
strava_integration = StravaIntegration(r.user)
|
||||||
if job == 0: # pragma: no cover
|
jobid = strava_integration.get_workout(stravaid)
|
||||||
|
if jobid == 0: # pragma: no cover
|
||||||
dologging('strava_webhooks.log',
|
dologging('strava_webhooks.log',
|
||||||
'Strava strava_open yielded NoTokenError')
|
'Strava strava_open yielded NoTokenError')
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
@@ -1801,7 +1739,7 @@ importauthorizeviews = {
|
|||||||
|
|
||||||
importsources = {
|
importsources = {
|
||||||
'c2': C2Integration,
|
'c2': C2Integration,
|
||||||
'strava': stravastuff,
|
'strava': StravaIntegration,
|
||||||
'polar': polarstuff,
|
'polar': polarstuff,
|
||||||
'ownapi': ownapistuff,
|
'ownapi': ownapistuff,
|
||||||
'sporttracks': sporttracksstuff,
|
'sporttracks': sporttracksstuff,
|
||||||
@@ -1960,24 +1898,3 @@ def workout_getsporttracksworkout_all(request):
|
|||||||
url = reverse('workouts_view')
|
url = reverse('workouts_view')
|
||||||
return HttpResponseRedirect(url)
|
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.sporttracksstuff import sporttracks_open
|
||||||
from rowers.tpstuff import tp_open
|
from rowers.tpstuff import tp_open
|
||||||
from iso8601 import ParseError
|
from iso8601 import ParseError
|
||||||
import rowers.stravastuff as stravastuff
|
|
||||||
import rowers.rojabo_stuff as rojabo_stuff
|
import rowers.rojabo_stuff as rojabo_stuff
|
||||||
import rowers.garmin_stuff as garmin_stuff
|
import rowers.garmin_stuff as garmin_stuff
|
||||||
from rowers.stravastuff import strava_open
|
|
||||||
from rowers.rojabo_stuff import rojabo_open
|
from rowers.rojabo_stuff import rojabo_open
|
||||||
import rowers.polarstuff as polarstuff
|
import rowers.polarstuff as polarstuff
|
||||||
import rowers.sporttracksstuff as sporttracksstuff
|
import rowers.sporttracksstuff as sporttracksstuff
|
||||||
|
|||||||
@@ -2566,7 +2566,7 @@ def workout_smoothenpace_view(request, id=0, message="", successmessage=""):
|
|||||||
if 'originalvelo' not in row.df:
|
if 'originalvelo' not in row.df:
|
||||||
row.df['originalvelo'] = velo
|
row.df['originalvelo'] = velo
|
||||||
|
|
||||||
velo2 = stravastuff.ewmovingaverage(velo, 5)
|
velo2 = utils.ewmovingaverage(velo, 5)
|
||||||
|
|
||||||
pace2 = 500./abs(velo2)
|
pace2 = 500./abs(velo2)
|
||||||
|
|
||||||
@@ -5641,10 +5641,9 @@ def workout_upload_view(request,
|
|||||||
messages.error(request, message)
|
messages.error(request, message)
|
||||||
|
|
||||||
if (upload_to_strava): # pragma: no cover
|
if (upload_to_strava): # pragma: no cover
|
||||||
|
strava_integration = StravaIntegration(request.user)
|
||||||
try:
|
try:
|
||||||
message, id = stravastuff.workout_strava_upload(
|
id = strava_integration.workout_export(w)
|
||||||
request.user, w,
|
|
||||||
)
|
|
||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
id = 0
|
id = 0
|
||||||
message = "Please connect to Strava first"
|
message = "Please connect to Strava first"
|
||||||
|
|||||||
Reference in New Issue
Block a user