From 91583134d228ac1c042d2cea021ce595881bafb1 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 13 Feb 2023 22:50:01 +0100 Subject: [PATCH] sporttracks, not fully tested --- rowers/integrations/integrations.py | 2 +- rowers/integrations/sporttracks.py | 237 +++++++++++++++-- rowers/sporttracksstuff.py | 350 -------------------------- rowers/tasks.py | 17 +- rowers/tests/statements.py | 2 +- rowers/tests/test_imports.py | 30 +-- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes rowers/uploads.py | 12 +- rowers/views/importviews.py | 194 ++++---------- rowers/views/statements.py | 2 - rowers/views/workoutviews.py | 5 +- 11 files changed, 290 insertions(+), 561 deletions(-) delete mode 100644 rowers/sporttracksstuff.py diff --git a/rowers/integrations/integrations.py b/rowers/integrations/integrations.py index 8434ce1e..d014a221 100644 --- a/rowers/integrations/integrations.py +++ b/rowers/integrations/integrations.py @@ -71,7 +71,7 @@ class SyncIntegration(metaclass=ABCMeta): "scope": self.oauth_data['scope'], "state": state} - url = self.oauth_data['authorizaton_uri']+urllib.parse.urlencode(params) + url = self.oauth_data['authorization_uri']+urllib.parse.urlencode(params) return url diff --git a/rowers/integrations/sporttracks.py b/rowers/integrations/sporttracks.py index e6dcbccb..e790f9e2 100644 --- a/rowers/integrations/sporttracks.py +++ b/rowers/integrations/sporttracks.py @@ -1,7 +1,9 @@ from .integrations import SyncIntegration, NoTokenError from rowers.models import User, Rower, Workout, TombStone -from rowers.tasks import handle_sporttracks_sync +from rowingdata import rowingdata + +from rowers.tasks import handle_sporttracks_sync, handle_sporttracks_workout_from_data from rowers.rower_rules import is_workout_user import rowers.mytypes as mytypes from rowsandall_app.settings import ( @@ -11,13 +13,38 @@ from rowsandall_app.settings import ( import re import numpy +import pytz +import json +import requests +import datetime +import pandas as pd import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('high') -from rowers.utils import myqueue, dologging +from rowers.utils import myqueue, dologging, uniqify + +def getidfromuri(uri): # pragma: no cover + m = re.search('/(\w.*)\/(\d+)', uri) + return m.group(2) + +def getidfromresponse(response): # pragma: no cover + t = response.json() + uri = t['uris'][0] + regex = '.*?sporttracks\.mobi\/api\/v2\/fitnessActivities/(\d+)\.json$' + m = re.compile(regex).match(uri).group(1) + + id = int(m) + + return int(id) + + +def default(o): # pragma: no cover + if isinstance(o, numpy.int64): + return int(o) + raise TypeError class SportTracksIntegration(SyncIntegration): def __init__(self, *args, **kwargs): @@ -27,7 +54,7 @@ class SportTracksIntegration(SyncIntegration): 'client_id': SPORTTRACKS_CLIENT_ID, 'client_secret': SPORTTRACKS_CLIENT_SECRET, 'redirect_uri': SPORTTRACKS_REDIRECT_URI, - 'autorization_uri': "https://api.sporttracks.mobi/oauth2/authorize", + 'authorization_uri': "https://api.sporttracks.mobi/oauth2/authorize", 'content_type': 'application/json', 'tokenname': 'sporttrackstoken', 'refreshtokenname': 'sporttracksrefreshtoken', @@ -42,13 +69,178 @@ class SportTracksIntegration(SyncIntegration): return super(SportTracksIntegration, self).open(*args, **kwargs) def createworkoutdata(self, w, *args, **kwargs): - return None + timezone = pytz.timezone(w.timezone) + + filename = w.csvfilename + try: + row = rowingdata(csvfile=filename) + except: # pragma: no cover + return {} + + try: + averagehr = int(row.df[' HRCur (bpm)'].mean()) + maxhr = int(row.df[' HRCur (bpm)'].max()) + except KeyError: # pragma: no cover + averagehr = 0 + maxhr = 0 + + try: + duration = w.duration.hour*3600 + duration += w.duration.minute*60 + duration += w.duration.second + duration += +1.0e-6*w.duration.microsecond + except AttributeError: # pragma: no cover + return {} + + t = row.df.loc[:, 'TimeStamp (sec)'].values - \ + row.df.loc[:, 'TimeStamp (sec)'].iloc[0] + try: + t[0] = t[1] + except IndexError: # pragma: no cover + return {} + + d = row.df.loc[:, 'cum_dist'].values + d[0] = d[1] + t = t.astype(int) + d = d.astype(int) + spm = row.df[' Cadence (stokes/min)'].astype(int).values + spm[0] = spm[1] + hr = row.df[' HRCur (bpm)'].astype(int).values + + haslatlon = 1 + + try: + lat = row.df[' latitude'].values + lon = row.df[' longitude'].values + if not lat.std() and not lon.std(): # pragma: no cover + haslatlon = 0 + except KeyError: + haslatlon = 0 + + haspower = 1 + try: + power = row.df[' Power (watts)'].astype(int).values + except KeyError: # pragma: no cover + haspower = 0 + + locdata = [] + hrdata = [] + spmdata = [] + distancedata = [] + powerdata = [] + + t = t.tolist() + hr = hr.tolist() + d = d.tolist() + spm = spm.tolist() + if haslatlon: + lat = lat.tolist() + lon = lon.tolist() + power = power.tolist() + + for i in range(len(t)): + hrdata.append(t[i]) + hrdata.append(hr[i]) + distancedata.append(t[i]) + distancedata.append(d[i]) + spmdata.append(t[i]) + spmdata.append(spm[i]) + if haslatlon: + locdata.append(t[i]) + locdata.append([lat[i], lon[i]]) + if haspower: + powerdata.append(t[i]) + powerdata.append(power[i]) + + try: + w.notes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com' + except TypeError: + w.notes = 'from '+w.workoutsource+' via rowsandall.com' + + st = w.startdatetime.astimezone(timezone) + st = st.replace(microsecond=0) + + data = { + "type": "Rowing", + "name": w.name, + "start_time": st.isoformat(), + "total_distance": int(w.distance), + "duration": duration, + "notes": w.notes, + "avg_heartrate": averagehr, + "max_heartrate": maxhr, + "distance": distancedata, + "cadence": spmdata, + "heartrate": hrdata, + } + + if haslatlon: + data = { + "type": "Rowing", + "name": w.name, + "start_time": st.isoformat(), + "total_distance": int(w.distance), + "duration": duration, + "notes": w.notes, + "avg_heartrate": averagehr, + "max_heartrate": maxhr, + "location": locdata, + "distance": distancedata, + "cadence": spmdata, + "heartrate": hrdata, + } + + + if haspower: + data['power'] = powerdata + + return data + def workout_export(self, workout, *args, **kwargs) -> str: - pass + thetoken = self.open() + stid = "0" + # ready to upload. Hurray + + if not(is_workout_user(self.user, workout)): + return "0" + + authorizationstring = str('Bearer ' + thetoken) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json'} + + data = self.createworkoutdata(workout) + + if not data: + return "0" + + url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json" + _ = myqueue( + queue, + handle_sporttracks_sync, + workout.id, + url, + headers, + json.dumps(data, default=default)) + + return 1 def get_workouts(self, *args, **kwargs) -> int: - pass + r = self.rower + workouts_json = self.get_workout_list_json(*args, **kwargs) + + stids = [int(getidfromuri(item['uri'])) + for item in workouts_json['items']] + + knownstids = uniqify([ + w.uploadedtosporttracks for w in Workout.objects.filter(user=r) + ]) + newids = [stid for stid in stids if stid not in knownstids] + for sporttracksid in newids: + id = self.get_workout(sporttracksid) + + return 1 @@ -57,33 +249,19 @@ class SportTracksIntegration(SyncIntegration): r = self.rower - authorizationstring = str('Bearer ' + r.sporttrackstoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - url = "https://api.sporttracks.mobi/api/v2/fitnessActivities/" + \ - str(sporttracksid) - s = requests.get(url, headers=headers) - - data = s.json() - strokedata = pd.DataFrame.from_dict({ - key: pd.Series(value, dtype='object') for key, value in data.items() - }) - - id = myqueue( + job = myqueue( queue, handle_sporttracks_workout_from_data, self.user, - sporttracksid, data, - strokedata, + id, 'sporttracks', 'sporttracks' ) - return id + + return job.id - - def get_workout_list(self, *args, **kwargs) -> list: + def get_workout_list_json(self, *args, **kwargs) -> dict: _ = self.open() r = self.rower @@ -98,13 +276,18 @@ class SportTracksIntegration(SyncIntegration): s = "Token doesn't exist. Need to authorize" raise NoTokenError(s) + return res.json() + + def get_workout_list(self, *args, **kwargs) -> list: + r = self.rower + workouts_json = self.get_workout_list_json(*args, **kwargs) workouts = [] knownstids = uniqify([ w.uploadedtosporttracks for w in Workout.objects.filter(user=r) ]) - for item in res.json()['items']: + for item in workouts_json['items']: d = int(float(item['total_distance'])) i = int(getidfromuri(item['uri'])) if i in knownstids: # pragma: no cover @@ -127,7 +310,7 @@ class SportTracksIntegration(SyncIntegration): return super(SportTracksIntegration, self).make_authorization_url(*args, **kwargs) def get_token(self, code, *args, **kwargs) -> (str, int, str): - return "" + return super(SportTracksIntegration, self).get_token(code, *args, **kwargs) diff --git a/rowers/sporttracksstuff.py b/rowers/sporttracksstuff.py deleted file mode 100644 index e21cae76..00000000 --- a/rowers/sporttracksstuff.py +++ /dev/null @@ -1,350 +0,0 @@ -from rowers.tasks import handle_sporttracks_sync, handle_sporttracks_workout_from_data -from rowers.rower_rules import is_workout_user -import rowers.mytypes as mytypes -from rowsandall_app.settings import ( - C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, - STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, - SPORTTRACKS_CLIENT_SECRET, SPORTTRACKS_CLIENT_ID, - SPORTTRACKS_REDIRECT_URI -) -import re -from rowers.imports import * -from rowers.utils import myqueue - -# All the functionality to connect to SportTracks - -import numpy - -import django_rq -queue = django_rq.get_queue('default') -queuelow = django_rq.get_queue('low') -queuehigh = django_rq.get_queue('low') - - -oauth_data = { - 'client_id': SPORTTRACKS_CLIENT_ID, - 'client_secret': SPORTTRACKS_CLIENT_SECRET, - 'redirect_uri': SPORTTRACKS_REDIRECT_URI, - 'autorization_uri': "https://api.sporttracks.mobi/oauth2/authorize", - 'content_type': 'application/json', - 'tokenname': 'sporttrackstoken', - 'refreshtokenname': 'sporttracksrefreshtoken', - 'expirydatename': 'sporttrackstokenexpirydate', - 'bearer_auth': False, - 'base_url': "https://api.sporttracks.mobi/oauth2/token", - 'scope': 'write', -} - -# Checks if user has SportTracks token, renews them if they are expired - - -def sporttracks_open(user): - return imports_open(user, oauth_data) - - -# Refresh ST token using refresh token -def do_refresh_token(refreshtoken): - return imports_do_refresh_token(refreshtoken, oauth_data) - -# Exchange ST access code for long-lived ST access token - - -def get_token(code): - return imports_get_token(code, oauth_data) - -# Make authorization URL including random string - - -def make_authorization_url(request): # pragma: no cover - return imports_make_authorization_url(oauth_data) - -# This is token refresh. Looks for tokens in our database, then refreshes - - -def rower_sporttracks_token_refresh(user): # pragma: no cover - r = Rower.objects.get(user=user) - res = do_refresh_token(r.sporttracksrefreshtoken) - access_token = res[0] - expires_in = res[1] - refresh_token = res[2] - expirydatetime = timezone.now()+timedelta(seconds=expires_in) - - r = Rower.objects.get(user=user) - r.sporttrackstoken = access_token - r.tokenexpirydate = expirydatetime - r.sporttracksrefreshtoken = refresh_token - - r.save() - - return r.sporttrackstoken - -# Get list of workouts available on SportTracks - - -def get_sporttracks_workout_list(user): - r = Rower.objects.get(user=user) - if (r.sporttrackstoken == '') or (r.sporttrackstoken is None): - s = "Token doesn't exist. Need to authorize" - return custom_exception_handler(401, s) - elif (timezone.now() > r.sporttrackstokenexpirydate): # pragma: no cover - s = "Token expired. Needs to refresh." - return custom_exception_handler(401, s) - else: - # ready to fetch. Hurray - authorizationstring = str('Bearer ' + r.sporttrackstoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - url = "https://api.sporttracks.mobi/api/v2/fitnessActivities" - s = requests.get(url, headers=headers) - - return s - -# Get workout summary data by SportTracks ID - - -def get_workout(user, sporttracksid, do_async=False): - r = Rower.objects.get(user=user) - if (r.sporttrackstoken == '') or (r.sporttrackstoken is None): # pragma: no cover - return custom_exception_handler(401, s) - s = "Token doesn't exist. Need to authorize" - elif (timezone.now() > r.sporttrackstokenexpirydate): # pragma: no cover - s = "Token expired. Needs to refresh." - return custom_exception_handler(401, s) - else: - # ready to fetch. Hurray - authorizationstring = str('Bearer ' + r.sporttrackstoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - url = "https://api.sporttracks.mobi/api/v2/fitnessActivities/" + \ - str(sporttracksid) - s = requests.get(url, headers=headers) - - data = s.json() - - strokedata = pd.DataFrame.from_dict({ - key: pd.Series(value, dtype='object') for key, value in data.items() - }) - - id= myqueue( - queue, - handle_sporttracks_workout_from_data, - user, - sporttracksid, data, - strokedata, - 'sporttracks', - 'sporttracks') - - return id - -# Create Workout Data for upload to SportTracks - - -def createsporttracksworkoutdata(w): - timezone = pytz.timezone(w.timezone) - - filename = w.csvfilename - try: - row = rowingdata(csvfile=filename) - except: # pragma: no cover - return 0 - - try: - averagehr = int(row.df[' HRCur (bpm)'].mean()) - maxhr = int(row.df[' HRCur (bpm)'].max()) - except KeyError: # pragma: no cover - averagehr = 0 - maxhr = 0 - - try: - duration = w.duration.hour*3600 - duration += w.duration.minute*60 - duration += w.duration.second - duration += +1.0e-6*w.duration.microsecond - except AttributeError: # pragma: no cover - return 0 - - # adding diff, trying to see if this is valid - # t = row.df.loc[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)'] - t = row.df.loc[:, 'TimeStamp (sec)'].values - \ - row.df.loc[:, 'TimeStamp (sec)'].iloc[0] - try: - t[0] = t[1] - except IndexError: # pragma: no cover - return 0 - - d = row.df.loc[:, 'cum_dist'].values - d[0] = d[1] - t = t.astype(int) - d = d.astype(int) - spm = row.df[' Cadence (stokes/min)'].astype(int).values - spm[0] = spm[1] - hr = row.df[' HRCur (bpm)'].astype(int).values - - haslatlon = 1 - - try: - lat = row.df[' latitude'].values - lon = row.df[' longitude'].values - if not lat.std() and not lon.std(): # pragma: no cover - haslatlon = 0 - except KeyError: - haslatlon = 0 - - haspower = 1 - try: - power = row.df[' Power (watts)'].astype(int).values - except KeyError: # pragma: no cover - haspower = 0 - - locdata = [] - hrdata = [] - spmdata = [] - distancedata = [] - powerdata = [] - - t = t.tolist() - hr = hr.tolist() - d = d.tolist() - spm = spm.tolist() - if haslatlon: - lat = lat.tolist() - lon = lon.tolist() - power = power.tolist() - - for i in range(len(t)): - hrdata.append(t[i]) - hrdata.append(hr[i]) - distancedata.append(t[i]) - distancedata.append(d[i]) - spmdata.append(t[i]) - spmdata.append(spm[i]) - if haslatlon: - locdata.append(t[i]) - locdata.append([lat[i], lon[i]]) - if haspower: - powerdata.append(t[i]) - powerdata.append(power[i]) - - try: - w.notes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com' - except TypeError: - w.notes = 'from '+w.workoutsource+' via rowsandall.com' - - st = w.startdatetime.astimezone(timezone) - st = st.replace(microsecond=0) - - if haslatlon: - data = { - "type": "Rowing", - "name": w.name, - "start_time": st.isoformat(), - "total_distance": int(w.distance), - "duration": duration, - "notes": w.notes, - "avg_heartrate": averagehr, - "max_heartrate": maxhr, - "location": locdata, - "distance": distancedata, - "cadence": spmdata, - "heartrate": hrdata, - } - else: - data = { - "type": "Rowing", - "name": w.name, - "start_time": st.isoformat(), - "total_distance": int(w.distance), - "duration": duration, - "notes": w.notes, - "avg_heartrate": averagehr, - "max_heartrate": maxhr, - "distance": distancedata, - "cadence": spmdata, - "heartrate": hrdata, - } - - if haspower: - data['power'] = powerdata - - return data - -# Obtain SportTracks Workout ID from the response returned on successful -# upload - - -def getidfromresponse(response): # pragma: no cover - t = response.json() - uri = t['uris'][0] - regex = '.*?sporttracks\.mobi\/api\/v2\/fitnessActivities/(\d+)\.json$' - m = re.compile(regex).match(uri).group(1) - - id = int(m) - - return int(id) - - -def default(o): # pragma: no cover - if isinstance(o, numpy.int64): - return int(o) - raise TypeError - - -def workout_sporttracks_upload(user, w, asynchron=False): # pragma: no cover - message = "Uploading to SportTracks" - stid = 0 - # ready to upload. Hurray - - thetoken = sporttracks_open(user) - - if (is_workout_user(user, w)): - data = createsporttracksworkoutdata(w) - if not data: - message = "Data error" - stid = 0 - return message, stid - - authorizationstring = str('Bearer ' + thetoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - - url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json" - if asynchron: - _ = myqueue(queue, handle_sporttracks_sync, - w.id, url, headers, json.dumps(data, default=default)) - return "Asynchronous sync", 0 - - response = requests.post(url, headers=headers, - data=json.dumps(data, default=default)) - - # check for duplicate error first - if (response.status_code == 409): - message = "Duplicate error" - w.uploadedtosporttracks = -1 - stid = -1 - w.save() - return message, stid - elif (response.status_code == 201 or response.status_code == 200): - s = response.json() - stid = getidfromresponse(response) - w.uploadedtosporttracks = stid - w.save() - return 'Successfully synced to SportTracks', stid - else: - s = response - message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason - stid = 0 - return message, stid - - else: - message = "You are not authorized to upload this workout" - stid = 0 - return message, stid - - return message, stid - -# Create workout from SportTracks Data, which are slightly different -# than Strava or Concept2 data - diff --git a/rowers/tasks.py b/rowers/tasks.py index 7e52311a..a327a511 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -458,9 +458,24 @@ def splitstdata(lijst): return [np.array(t), np.array(latlong)] @app.task -def handle_sporttracks_workout_from_data(user, importid, data, strokedata, source, +def handle_sporttracks_workout_from_data(user, importid, source, workoutsource, debug=False, **kwargs): + r = user.rower + authorizationstring = str('Bearer ' + r.sporttrackstoken) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json'} + url = "https://api.sporttracks.mobi/api/v2/fitnessActivities/" + \ + str(importid) + s = requests.get(url, headers=headers) + + data = s.json() + + strokedata = pd.DataFrame.from_dict({ + key: pd.Series(value, dtype='object') for key, value in data.items() + }) + try: workouttype = data['type'] except KeyError: # pragma: no cover diff --git a/rowers/tests/statements.py b/rowers/tests/statements.py index f78857cf..7a18725d 100644 --- a/rowers/tests/statements.py +++ b/rowers/tests/statements.py @@ -64,7 +64,7 @@ from mock import Mock, patch #from minimocktest import MockTestCase import pandas as pd import rowers.c2stuff as c2stuff -import rowers.sporttracksstuff as sporttracksstuff + import rowers.rojabo_stuff as rojabo_stuff from django.urls import reverse, reverse_lazy diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index 8426aee5..6a2f5ee2 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -1236,7 +1236,7 @@ class STObjects(DjangoTestCase): csvfilename=filename ) - @patch('rowers.sporttracksstuff.requests.post', side_effect=mocked_requests) + @patch('rowers.integrations.sporttracks.requests.post', side_effect=mocked_requests) def test_sporttracks_callback(self, mock_post): response = self.c.get('/sporttracks_callback?code=dsdoij232s',follow=True) @@ -1244,15 +1244,15 @@ class STObjects(DjangoTestCase): self.assertEqual(response.status_code, 200) - @patch('rowers.sporttracksstuff.requests.post', side_effect=mocked_requests) + @patch('rowers.integrations.sporttracks.requests.post', side_effect=mocked_requests) def test_sporttracks_token_refresh(self, mock_post): response = self.c.get('/rowers/me/sporttracksrefresh/',follow=True) self.assertEqual(response.status_code, 200) - @patch('rowers.sporttracksstuff.requests.post', side_effect=mocked_requests) - @patch('rowers.sporttracksstuff.requests.get', side_effect=mocked_requests) + @patch('rowers.integrations.sporttracks.requests.post', side_effect=mocked_requests) + @patch('rowers.integrations.sporttracks.requests.get', side_effect=mocked_requests) def test_sporttracks_upload(self, mock_get, mock_post): response = self.c.get('/rowers/workout/'+encoded1+'/sporttracksuploadw/') @@ -1263,7 +1263,7 @@ class STObjects(DjangoTestCase): self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/') self.assertEqual(response.status_code, 302) - @patch('rowers.sporttracksstuff.requests.get', side_effect=mocked_requests) + @patch('rowers.integrations.sporttracks.requests.get', side_effect=mocked_requests) def test_sporttracks_list(self, mock_get): response = self.c.get('/rowers/workout/sporttracksimport',follow=True) @@ -1306,26 +1306,6 @@ class STObjects(DjangoTestCase): self.assertEqual(response.status_code, 200) - @patch('rowers.dataprep.create_engine') - def test_strokedata(self, mocked_sqlalchemy): - with open('rowers/tests/testdata/sporttrackstestdata.txt','r') as infile: - data = json.load(infile) - - from rowers.sporttracksstuff import add_workout_from_data - - res = add_workout_from_data(self.u,1,data,data) - - @patch('rowers.dataprep.create_engine') - def test_strokedatanohr(self, mocked_sqlalchemy): - with open('rowers/tests/testdata/sporttrackstestnohr.txt','r') as infile: - data = json.load(infile) - - from rowers.sporttracksstuff import add_workout_from_data - - - - res = add_workout_from_data(self.u,1,data,data) - #@pytest.mark.django_db diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index e3322f74f34401df6bc7b02ad7ae7912eb951e78..f87905524517a7050d53b8859644520469a3beb5 100644 GIT binary patch delta 3906 zcmV-I554f9AE6%yABzYGfUoM22OkxRnscdB7sogvkkujq8wHfj+p1$GkvkzUAK#sx z?7dlDY*uIM`*+d6-Mt?V?;O0i=vM31`uO2;-7ha5b$$0ow^{C`o!6WDk&G={2S@$t z&8mO@^Sgez&Nq1F1n)Mh&(83c&Dq8Af&g&w`_m`Sx2L$bT5tMpeY8wRZC3lc^FBE{ z>iX@kz0nQq>5qCt=H2GyUmy7Qu7B8_oL#Jzo5dUa>;3(Z@8A7r|7v-0 zclTxb`ti#{0(AF4?h(8|-nY0<&O}EX;co!@00)b-)5ravbmx1|`|hHDu{vGezx!9m zUS232`r@qbPV!;Tmq%yomzzUjf3Wy!>!$-At#)tF(`EXxn{=P~UcT7)Y44Bt4iUq_ z;_}m{9Udf6>yDRy|5&EG|F!FvKb@a$U(NpgdHN-nn;)-#T6X>Bm*vIN)%tb6{C9ul zN2JsI)}6dw9->Vb+5P$H3X5wlapmi$-MiK4>(l?!4T!tu2Clipm9HPKx36md{&4$N zEECzwEmt4p%Vm1NhlKet?*6_)@nUs+oNjH|zdCz)__SMpzwS;F#m}~0-Mc&b?`LDI=ouy3?@mt zSOs-tgVVr&o*#5Y+{snMJwZfpLBXAp-c*o#xH)&KOWv(i#N8QURE_d!h~R?U<7~J) zTM_q|I+_5U|0V?7yiF*<%jW+L%n+W&1 z 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 Pc;|oY=e?H%#DD<+#Bu1q diff --git a/rowers/uploads.py b/rowers/uploads.py index d0eb510f..3a784473 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -2,7 +2,6 @@ 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 from rowers.integrations import * from rowers.utils import ( @@ -131,7 +130,6 @@ def make_plot(r, w, f1, f2, plottype, title, imagename='', plotnr=0): def do_sync(w, options, quick=False): - do_strava_export = w.user.strava_auto_export try: do_strava_export = options['upload_to_Strava'] or do_strava_export @@ -236,8 +234,10 @@ def do_sync(w, options, quick=False): f.write(str(e)) do_st_export = w.user.sporttracks_auto_export + if options['sporttracksid'] != 0 and options['sporttracksid'] != '': - w.uploadedtost = options['sporttracksid'] + w.uploadedtosporttracks = options['sporttracksid'] + w.save() do_st_export = False try: # pragma: no cover @@ -248,9 +248,9 @@ def do_sync(w, options, quick=False): if do_st_export: # pragma: no cover try: - message, id = sporttracksstuff.workout_sporttracks_upload( - w.user.user, w, asynchron=True, - ) + st_integration = SportTracksIntegration(w.user.user) + id = st_integration.workout_export(w) + dologging('st_export.log', 'exported workout {wid} for user {uid}'.format( wid = w.id, diff --git a/rowers/views/importviews.py b/rowers/views/importviews.py index 953169bb..56dfc70b 100644 --- a/rowers/views/importviews.py +++ b/rowers/views/importviews.py @@ -138,13 +138,13 @@ def workout_c2_upload_view(request, id=0): # Upload workout to SportTracks @permission_required('workout.change_workout', fn=get_workout_by_opaqueid) def workout_sporttracks_upload_view(request, id=0): - message = "" + st_integration = SportTracksIntegration(request.user) + # ready to upload. Hurray w = get_workout(id) r = w.user - message, res = sporttracksstuff.workout_sporttracks_upload( - r.user, w, asynchron=True) + id = st_integration.workout_export(w) messages.info( request, 'Your workout will be synchronized with SportTracks in the background') @@ -240,16 +240,8 @@ def rower_polar_authorize(request): # pragma: no cover def rower_sporttracks_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": SPORTTRACKS_CLIENT_ID, - "response_type": "code", - "state": state, - "redirect_uri": SPORTTRACKS_REDIRECT_URI} - - url = "https://api.sporttracks.mobi/oauth2/authorize?" + \ - urllib.parse.urlencode(params) + st_integration = SportTracksIntegration(request.user) + url = st_integration.make_authorization_url() return HttpResponseRedirect(url) @@ -335,24 +327,8 @@ def rower_tp_token_refresh(request): # SportTracks token refresh. URL for manual refresh. Not visible to users @login_required() def rower_sporttracks_token_refresh(request): - r = getrower(request.user) - res = sporttracksstuff.do_refresh_token( - r.sporttracksrefreshtoken, - ) - access_token = res[0] - expires_in = res[1] - refresh_token = res[2] - expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) - - r = getrower(request.user) - r.sporttrackstoken = access_token - r.sporttrackstokenexpirydate = expirydatetime - r.sporttracksrefreshtoken = refresh_token - - r.save() - - successmessage = "Tokens refreshed. Good to go" - messages.info(request, successmessage) + st_integration = SportTracksIntegration(request.user) + result = st_integration.token_refresh() url = reverse('workouts_view') @@ -725,20 +701,8 @@ def rower_process_sporttrackscallback(request): url = reverse('rower_exportsettings_view') return HttpResponseRedirect(url) - res = sporttracksstuff.get_token(code) - - access_token = res[0] - expires_in = res[1] - refresh_token = res[2] - - expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) - - r = getrower(request.user) - r.sporttrackstoken = access_token - r.sporttrackstokenexpirydate = expirydatetime - r.sporttracksrefreshtoken = refresh_token - - r.save() + st_integration = SportTracksIntegration(request.user) + token = st_integration.get_token(code) successmessage = "Tokens stored. Good to go. Please check your import/export settings" messages.info(request, successmessage) @@ -1100,17 +1064,8 @@ def workout_stravaimport_view(request, message="", userid=0): alldata = {} 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 - ) + res = strava_integration.get_workout(id) + # done, redirect to workouts list messages.info(request, 'Your Strava workouts will be imported in the background.' @@ -1416,54 +1371,34 @@ def workout_polarimport_view(request, userid=0): # pragma: no cover @permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True) @permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True) def workout_sporttracksimport_view(request, message="", userid=0): - r = getrequestrower(request, userid=userid) - if r.user != request.user: - messages.error( - request, 'You can only access your own workouts on the NK Logbook, not those of your athletes') - url = reverse('workout_sporttracksimport_view', - kwargs={'userid': request.user.id}) - return HttpResponseRedirect(url) + st_integration = SportTracksIntegration(request.user) + try: + _ = st_integration.open() + except NoTokenError: + return HttpResponseRedirect("/rowers/me/sporttracksauthorize/") - res = sporttracksstuff.get_sporttracks_workout_list(request.user) - if (res.status_code != 200): - if (res.status_code == 401): - r = getrower(request.user) - if (r.sporttrackstoken == '') or (r.sporttrackstoken is None): - s = "Token doesn't exist. Need to authorize" - return HttpResponseRedirect("/rowers/me/sporttracksauthorize/") - else: # pragma: no cover - return HttpResponseRedirect("/rowers/me/sporttracksrefresh/") - message = "Something went wrong in workout_sporttracksimport_view" # pragma: no cover - messages.error(request, message) # pragma: no cover - if settings.DEBUG: # pragma: no cover - return HttpResponse(res) - else: # pragma: no cover - url = reverse('workouts_view') - return HttpResponseRedirect(url) - - workouts = [] - - knownstids = uniqify([ - w.uploadedtosporttracks for w in Workout.objects.filter(user=r) - ]) - for item in res.json()['items']: - d = int(float(item['total_distance'])) - i = int(getidfromuri(item['uri'])) - if i in knownstids: # pragma: no cover - nnn = '' - else: - nnn = 'NEW' - n = item['name'] - ttot = str(datetime.timedelta(seconds=int(float(item['duration'])))) - s = item['start_time'] - r = item['type'] - keys = ['id', 'distance', 'duration', - 'starttime', 'type', 'name', 'new'] - values = [i, d, ttot, s, r, n, nnn] - res = dict(zip(keys, values)) - workouts.append(res) + workouts = st_integration.get_workout_list() r = getrower(request.user) + + if request.method == "POST": + try: # pragma: no cover + tdict = dict(request.POST.lists()) + ids = tdict['workoutid'] + stids = [int(id) for id in ids] + alldata = {} + + for id in stids: + res = st_integration.get_workout(id) + + # done, redirect to workouts list + messages.info(request, + 'Your SportTracks 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 = [ { @@ -1476,12 +1411,17 @@ def workout_sporttracksimport_view(request, message="", userid=0): }, ] - return render(request, 'sporttracks_list_import.html', + checknew = request.GET.get('selectallnew', False) + + + return render(request, 'list_import.html', {'workouts': workouts, 'breadcrumbs': breadcrumbs, 'active': 'nav-workouts', 'rower': r, 'teams': get_my_teams(request.user), + 'integration':'SportTracks', + 'checknew': checknew, }) return HttpResponse(res) # pragma: no cover @@ -1630,7 +1570,7 @@ importsources = { 'strava': StravaIntegration, 'polar': polarstuff, 'ownapi': ownapistuff, - 'sporttracks': sporttracksstuff, + 'sporttracks': SportTracksIntegration, 'trainingpeaks': tpstuff, 'nk': NKIntegration, } @@ -1698,27 +1638,9 @@ def workout_getimportview_old(request, externalid, source='c2', do_async=True): # Imports all new workouts from SportTracks @login_required() def workout_getsporttracksworkout_all(request): - res = sporttracksstuff.get_sporttracks_workout_list(request.user) - if (res.status_code == 200): - r = getrower(request.user) - stids = [int(getidfromuri(item['uri'])) - for item in res.json()['items']] - knownstids = uniqify([ - w.uploadedtosporttracks for w in Workout.objects.filter(user=r) - ]) - newids = [stid for stid in stids if stid not in knownstids] - for sporttracksid in newids: - id = sporttracksstuff.get_workout( - request.user, sporttracksid) - - if id == 0: # pragma: no cover - messages.error( - request, "Something went wrong with workout {id}".format(id=sporttracksid)) - - else: - w = Workout.objects.get(id=id) - w.uploadedtosporttracks = sporttracksid - w.save() + st_integration = SportTracksIntegration(request.user) + st_integration.get_workouts() + messages.info(request,"Your SportTracks workouts will be imported in the background") url = reverse('workouts_view') return HttpResponseRedirect(url) @@ -1761,27 +1683,9 @@ def workout_getimportview(request, externalid, source='c2', do_async=True): # Imports all new workouts from SportTracks @login_required() def workout_getsporttracksworkout_all(request): - res = sporttracksstuff.get_sporttracks_workout_list(request.user) - if (res.status_code == 200): - r = getrower(request.user) - stids = [int(getidfromuri(item['uri'])) - for item in res.json()['items']] - knownstids = uniqify([ - w.uploadedtosporttracks for w in Workout.objects.filter(user=r) - ]) - newids = [stid for stid in stids if stid not in knownstids] - for sporttracksid in newids: - id = sporttracksstuff.get_workout( - request.user, sporttracksid) - - if id == 0: # pragma: no cover - messages.error( - request, "Something went wrong with workout {id}".format(id=sporttracksid)) - - else: - w = Workout.objects.get(id=id) - w.uploadedtosporttracks = sporttracksid - w.save() + st_integration = SportTracksIntegration(request.user) + _ = st_integration.get_workouts() + messages.info(request,"Your SportTracks workouts will be imported in the background") url = reverse('workouts_view') return HttpResponseRedirect(url) diff --git a/rowers/views/statements.py b/rowers/views/statements.py index c5004b2d..7d65e69b 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -194,7 +194,6 @@ import datetime import iso8601 import rowers.rojabo_stuff as rojabo_stuff from rowers.rp3stuff import rp3_open -from rowers.sporttracksstuff import sporttracks_open from rowers.tpstuff import tp_open from iso8601 import ParseError @@ -203,7 +202,6 @@ import rowers.garmin_stuff as garmin_stuff from rowers.rojabo_stuff import rojabo_open import rowers.polarstuff as polarstuff -import rowers.sporttracksstuff as sporttracksstuff from rowers.integrations import * diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index b110322e..6e8d0a62 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -5654,10 +5654,9 @@ def workout_upload_view(request, messages.error(request, message) if (upload_to_st): # pragma: no cover + st_integration = SportTracksIntegration(request.user) try: - message, id = sporttracksstuff.workout_sporttracks_upload( - request.user, w - ) + id = st_integration.workout_export(w) except NoTokenError: message = "Please connect to SportTracks first" id = 0