From eaedf30369b3bca8c91363c943e22a8b4bb10585 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 11 Feb 2023 17:20:02 +0100
Subject: [PATCH] adding strava
---
rowers/integrations/__init__.py | 1 +
rowers/integrations/c2.py | 8 +-
rowers/integrations/integrations.py | 8 +-
rowers/integrations/strava.py | 344 ++++++++++++++++++
rowers/interactiveplots.py | 7 +-
rowers/management/commands/processemail.py | 2 +-
rowers/stravastuff.py | 404 ---------------------
rowers/templates/list_import.html | 10 +-
rowers/templatetags/rowerfilters.py | 4 +-
rowers/tests/test_api.py | 2 +-
rowers/tests/test_braintree.py | 2 +-
rowers/tests/test_imports.py | 45 +--
rowers/tests/test_permissions2.py | 2 +-
rowers/tests/test_unit_tests.py | 2 +-
rowers/tests/viewnames.csv | 2 +-
rowers/uploads.py | 7 +-
rowers/urls.py | 2 -
rowers/views/importviews.py | 237 ++++--------
rowers/views/statements.py | 4 +-
rowers/views/workoutviews.py | 7 +-
20 files changed, 474 insertions(+), 626 deletions(-)
create mode 100644 rowers/integrations/strava.py
delete mode 100644 rowers/stravastuff.py
diff --git a/rowers/integrations/__init__.py b/rowers/integrations/__init__.py
index 56112937..89ea5f85 100644
--- a/rowers/integrations/__init__.py
+++ b/rowers/integrations/__init__.py
@@ -1 +1,2 @@
from .c2 import C2Integration
+from .strava import StravaIntegration
diff --git a/rowers/integrations/c2.py b/rowers/integrations/c2.py
index 09af9f4a..1d5d3ea4 100644
--- a/rowers/integrations/c2.py
+++ b/rowers/integrations/c2.py
@@ -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)
diff --git a/rowers/integrations/integrations.py b/rowers/integrations/integrations.py
index d1fd8646..95bae300 100644
--- a/rowers/integrations/integrations.py
+++ b/rowers/integrations/integrations.py
@@ -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
diff --git a/rowers/integrations/strava.py b/rowers/integrations/strava.py
new file mode 100644
index 00000000..cedebb8b
--- /dev/null
+++ b/rowers/integrations/strava.py
@@ -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)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index ff5d1464..8235fc74 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -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
diff --git a/rowers/management/commands/processemail.py b/rowers/management/commands/processemail.py
index c9ff2cb1..a89c1eb9 100644
--- a/rowers/management/commands/processemail.py
+++ b/rowers/management/commands/processemail.py
@@ -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 *
diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py
deleted file mode 100644
index 4eab0961..00000000
--- a/rowers/stravastuff.py
+++ /dev/null
@@ -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
diff --git a/rowers/templates/list_import.html b/rowers/templates/list_import.html
index 27e1e36c..1a0f21c2 100644
--- a/rowers/templates/list_import.html
+++ b/rowers/templates/list_import.html
@@ -9,11 +9,12 @@
{% if workouts %}
+ {% if integration == 'C2 Logbook' %}
-
Import all NEW
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.
-
+
@@ -29,11 +30,12 @@
+ {% endif %}