From 652d9724113f960c640ccebe4f43f350992f1fcb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 4 May 2021 08:50:52 +0200 Subject: [PATCH 01/12] changes --- rowers/dataprep.py | 2 +- rowers/garmin_stuff.py | 19 +++++-------------- rowers/models.py | 2 +- rowers/utils.py | 18 ++++++++---------- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 790a83f1..66e63a01 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -2435,7 +2435,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True,workstrokesonly=True,comput else: try: df = pd.read_parquet(csvfilenames[0],columns=columns) - except (OSError,ArrowInvalid): + except (OSError,ArrowInvalid,IndexError): rowdata,row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): # pragma: no cover data = dataprep(rowdata.df,id=ids[0],bands=True,otwpower=True,barchart=True) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 16cfbc25..95a55024 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -2,6 +2,7 @@ from rowers.imports import * import datetime import requests +from requests import Session, Request from requests_oauthlib import OAuth1,OAuth1Session from requests_oauthlib.oauth1_session import TokenRequestDenied from requests import Request, Session @@ -302,27 +303,17 @@ def ps_to_garmin(ps,r): lijst.append(gstep) payload['steps'] = lijst + url = 'https://apis.garmin.com/training-api/workout/' + garmin = OAuth1Session(oauth_data['client_id'], client_secret=oauth_data['client_secret'], resource_owner_key=r.garmintoken, resource_owner_secret=r.garminrefreshtoken, - signature_method='HMAC-SHA1' + signature_method='HMAC-SHA1', + encoding='base64' ) - - url = 'https://apis.garmin.com/training-api/workout/' - - - garminauth = OAuth1( - client_key=oauth_data['client_id'], - client_secret=oauth_data['client_secret'], - resource_owner_key=r.garmintoken, - resource_owner_secret=r.garminrefreshtoken, - signature_method='HMAC-SHA1' - ) - - response = garmin.post(url,data=payload) #POST /training-api/workout?undefined HTTP/1.1 diff --git a/rowers/models.py b/rowers/models.py index 6f73da0b..efac1c2d 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -3158,7 +3158,7 @@ class Workout(models.Model): timezone = models.CharField(default='UTC', choices=timezones, max_length=100) - distance = models.IntegerField(default=0,blank=True) + distance = models.IntegerField(default=0) duration = models.TimeField(blank=True) dragfactor = models.IntegerField(default=0,blank=True) diff --git a/rowers/utils.py b/rowers/utils.py index 9fa0825b..949c2d98 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -589,18 +589,15 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7): seconds = 0 distance = 0 rscore = 0 - durationtype = step['durationType'] + durationtype = step.get('durationType',0) + value = step.get('durationValue',0) - if step['durationValue'] == 0: # pragma: no cover + if value == 0: # pragma: no cover return 0,0,0 - try: - targettype = step['targetType'] - except KeyError: # pragma: no cover - targettype = 0 + targettype = step.get('targetType',0) if durationtype == 'Time': - value = step['durationValue'] seconds = value/1000. distance = avgspeed*seconds rscore = 60.*seconds/3600. @@ -616,6 +613,8 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7): elif valuelow != 0 and valuehigh != 0: # pragma: no cover distance = seconds*(valuelow+valuehigh)/2. velomid = (valuelow+valuehigh)/2000. + else: + velomid = avgspeed veloratio = (velomid/ftv)**(3.0) rscoreperhour = 100.*veloratio @@ -657,11 +656,10 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7): rscore = 100*(avgpower/ftp)*seconds/3600. return seconds,distance,rscore - elif durationtype == 'Distance': # pragma: no cover - value = step['durationValue'] + elif durationtype == 'Distance': distance = value/100. seconds = distance/avgspeed - rscore = 60*seconds/3600. + rscore = 60.*float(seconds)/3600. if targettype == 'Speed': value = step.get('targetValue',0) From bf1bde95ad06d0e8235c2a27ec24c125bf23cbf9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 4 May 2021 09:40:24 +0200 Subject: [PATCH 02/12] coverage --- rowers/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rowers/utils.py b/rowers/utils.py index 949c2d98..4594c9ad 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -661,16 +661,16 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7): seconds = distance/avgspeed rscore = 60.*float(seconds)/3600. - if targettype == 'Speed': + if targettype == 'Speed': # pragma: no cover value = step.get('targetValue',0) valuelow = step.get('targetValueLow',0) valuehigh = step.get('targetValueHigh',0) velomid = 0 - if value != 0: # pragma: no cover + if value != 0: seconds = distance/value velomid = value/1000. - elif valuelow != 0 and valuehigh != 0: # pragma: no cover + elif valuelow != 0 and valuehigh != 0: velomid = (valuelow+valuehigh)/2000. seconds = distance/velomid @@ -678,7 +678,7 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7): rscoreperhour = 100.*veloratio rscore = rscoreperhour*seconds/3600. - if targettype == 'Power': + if targettype == 'Power': # pragma: no cover value = step.get('targetValue',0) valuelow = step.get('targetValueLow',0) valuehigh = step.get('targetValueHigh',0) From 5fc274e834bba255456e33288abbb54fbb98be43 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 5 May 2021 16:31:18 +0200 Subject: [PATCH 03/12] garmin work --- garminscript.py | 83 ++++++++++++++++++++++++++++++++++++++++++ rowers/garmin_stuff.py | 17 ++++++--- 2 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 garminscript.py diff --git a/garminscript.py b/garminscript.py new file mode 100644 index 00000000..a946efc8 --- /dev/null +++ b/garminscript.py @@ -0,0 +1,83 @@ +from requests_oauthlib import OAuth1Session, OAuth1 +import hmac +import hashlib + +import requests +from urllib.parse import quote_plus as rawurlencode + +import logging + +logging.basicConfig() +logging.getLogger().setLevel(logging.DEBUG) +requests_log = logging.getLogger("requests.packages.urllib3") +requests_log.setLevel(logging.DEBUG) +requests_log.propagate = True + + +payload = { + 'workoutName': '30min at threshold', + 'sport': 'GENERIC', + 'description': 'Uploaded from Rowsandall.com', + 'estimatedDurationInSecs': 1800, + 'estimatedDistanceInMeters': 6936, + 'workoutProvider': 'Rowsandall.com', + 'workoutSourceId': 'Rowsandall.com', + 'steps': [ + { + 'type': 'Step', 'stepOrder': 0, + 'repeatType': 'Step', + 'repeatValue': 1, + 'intensity': 'Active', + 'description': '0', + 'durationType': 'TIME', + 'durationValue': 1800, + 'durationValueType': '', + 'targetType': 'Power', + 'targetValue': 1226, + 'targetValueLow': 0, 'targetValueHigh': (0,) + } + ] + } + +payload = {} + +oauth_consumer_key = b'ca29ba5e-6868-4468-987d-4ee60a1f04bf' +oauth_consumer_secret = b'SKRqjML9mOBV7BcPpN7LsbuDNDtvLOvRiyo' +oauth_token = b'79454eab-bf82-4329-9de2-82a6bd911498' +oauth_token_secret = b'DihdHJ2ThEdbsyoStpPTEmYh5F52L697HhD' + +authheaders = OAuth1(client_key=oauth_consumer_key, + client_secret=oauth_consumer_secret, + resource_owner_key=oauth_token, + resource_owner_secret=oauth_token_secret, + signature_method='HMAC-SHA1', + #encoding='base64' + ) + + +url = 'https://apis.garmin.com/training-api/workout/' + +response = requests.post(url,payload,auth=authheaders) + +# build base_string +base_string1 = b'POST&https%3A%2F%2Fapis.garmin.com%2Ftraining-api%2Fworkout%2F&description%3DUploaded%2520from%2520Rowsandall.com%26estimatedDistanceInMeters%3D6936%26estimatedDurationInSecs%3D1800%26oauth_consumer_key%3Dca29ba5e-6868-4468-987d-4ee60a1f04bf%26oauth_nonce%3D163208869057942765101620205416%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1620205416%26oauth_token%3D79454eab-bf82-4329-9de2-82a6bd911498%26oauth_version%3D1.0%26sport%3DGENERIC%26steps%3Ddescription%26steps%3DdurationType%26steps%3DdurationValue%26steps%3DdurationValueType%26steps%3Dintensity%26steps%3DrepeatType%26steps%3DrepeatValue%26steps%3DstepOrder%26steps%3DtargetType%26steps%3DtargetValue%26steps%3DtargetValueHigh%26steps%3DtargetValueLow%26steps%3Dtype%26workoutName%3D30min%2520at%2520threshold%26workoutProvider%3DRowsandall.com%26workoutSourceId%3DRowsandall.com' + +base_stringa = b'POST&https%3A%2F%2Fapis.garmin.com%2Ftraining-api%2Fworkout%2F&oauth_consumer_key%3Dca29ba5e-6868-4468-987d-4ee60a1f04bf%26oauth_nonce%3D90559685229655402871620208938%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1620208938%26oauth_token%3D79454eab-bf82-4329-9de2-82a6bd911498%26oauth_version%3D1.0' +base_string2 = b'POST&https%3A%2F%2Fapis.garmin.com%2Ftraining-api%2Fworkout%2F&oauth_consumer_key%3Dca29ba5e-6868-4468-987d-4ee60a1f04bf%26oauth_nonce%3DIyk9Ambokd2%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1620138340%26oauth_token%3D673806b7-aa7b-4064-8290-2dd1b0236ae6%26oauth_version%3D1.0' + + +base_string3 = b'POST&https%3A%2F%2Fapis.garmin.com%2Ftraining-api%2Fworkout&oauth_consumer_key%3Dca29ba5e-6868-4468-987d-4ee60a1f04bf%26oauth_nonce%3D90559685229655402871620208938%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1620208938%26oauth_token%3D79454eab-bf82-4329-9de2-82a6bd911498%26oauth_version%3D1.0' + +auth = b'&'.join([oauth_consumer_secret,oauth_token_secret]) +signingKey = bytes(rawurlencode(oauth_consumer_secret) + "&" + rawurlencode(oauth_token_secret),'utf-8') +print(auth) +print(signingKey) +print(base_string3) + +sig = hmac.new(signingKey, base_string3, hashlib.sha1) + +print(response.status_code) +print(response.text) + + +print(sig.hexdigest().encode()) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 95a55024..65c6d7fe 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -35,11 +35,11 @@ from pytz import timezone as tz, utc #http_client.HTTPConnection.debuglevel = 1 # You must initialize logging, otherwise you'll not see debug output. -#logging.basicConfig() -#logging.getLogger().setLevel(logging.DEBUG) -#requests_log = logging.getLogger("requests.packages.urllib3") -#requests_log.setLevel(logging.DEBUG) -#requests_log.propagate = True +logging.basicConfig() +logging.getLogger().setLevel(logging.DEBUG) +requests_log = logging.getLogger("requests.packages.urllib3") +requests_log.setLevel(logging.DEBUG) +requests_log.propagate = True from rowers.tasks import handle_get_garmin_file @@ -305,6 +305,11 @@ def ps_to_garmin(ps,r): payload['steps'] = lijst url = 'https://apis.garmin.com/training-api/workout/' + print(payload) + print(oauth_data['client_id']) + print(oauth_data['client_secret']) + print(r.garmintoken) + print(r.garminrefreshtoken) garmin = OAuth1Session(oauth_data['client_id'], client_secret=oauth_data['client_secret'], @@ -314,6 +319,8 @@ def ps_to_garmin(ps,r): encoding='base64' ) + print(garmin.headers) + response = garmin.post(url,data=payload) #POST /training-api/workout?undefined HTTP/1.1 From 5fc34419b86e0064ad1baa88d6dfe967b014e1ce Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 10 May 2021 18:44:01 +0200 Subject: [PATCH 04/12] small improvement activity chart; retiring Bike, only bike with small b --- rowers/garmin_stuff.py | 17 +++++------------ rowers/interactiveplots.py | 6 ++++-- rowers/mytypes.py | 20 ++++++++++---------- rowers/tests/test_emails.py | 4 ++-- rowers/utils.py | 1 - 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 65c6d7fe..6d255b64 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -35,11 +35,11 @@ from pytz import timezone as tz, utc #http_client.HTTPConnection.debuglevel = 1 # You must initialize logging, otherwise you'll not see debug output. -logging.basicConfig() -logging.getLogger().setLevel(logging.DEBUG) -requests_log = logging.getLogger("requests.packages.urllib3") -requests_log.setLevel(logging.DEBUG) -requests_log.propagate = True +#logging.basicConfig() +#logging.getLogger().setLevel(logging.DEBUG) +#requests_log = logging.getLogger("requests.packages.urllib3") +#requests_log.setLevel(logging.DEBUG) +#requests_log.propagate = True from rowers.tasks import handle_get_garmin_file @@ -305,12 +305,6 @@ def ps_to_garmin(ps,r): payload['steps'] = lijst url = 'https://apis.garmin.com/training-api/workout/' - print(payload) - print(oauth_data['client_id']) - print(oauth_data['client_secret']) - print(r.garmintoken) - print(r.garminrefreshtoken) - garmin = OAuth1Session(oauth_data['client_id'], client_secret=oauth_data['client_secret'], resource_owner_key=r.garmintoken, @@ -319,7 +313,6 @@ def ps_to_garmin(ps,r): encoding='base64' ) - print(garmin.headers) response = garmin.post(url,data=payload) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index de8ef74c..56c2ac42 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -571,7 +571,6 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type',toolbar_lo while d<=enddate: dd = d.strftime('%d') - if totaldays<30: dates.append(d.strftime('%m/%d')) dates_sorting.append(d.strftime('%Y/%m/%d')) @@ -582,7 +581,10 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type',toolbar_lo rscores.append(0) trimps.append(0) links.append('') - types.append('rower') + try: + types.append(types[0]) + except IndexError: + types.append('rower') try: rowers.append(rowers[0]) diff --git a/rowers/mytypes.py b/rowers/mytypes.py index 1716d023..89ddb6af 100644 --- a/rowers/mytypes.py +++ b/rowers/mytypes.py @@ -21,7 +21,7 @@ workouttypes_ordered = collections.OrderedDict({ 'c-boat':'Dutch C boat', 'churchboat':'Finnish Church boat', 'Ride':'Ride', - 'Bike':'Bike', + 'bike':'Bike', 'Run':'Run', 'NordicSki':'NordicSki', 'Swim':'Swim', @@ -36,7 +36,7 @@ workouttypes_ordered = collections.OrderedDict({ 'Kayaking':'Kayaking', 'Workout':'Workout', 'Yoga':'Yoga', - 'bike':'Bike', +# 'bike':'Bike', 'other':'Other', } ) @@ -51,7 +51,7 @@ stravacollection = ( ('water','Rowing'), ('rower','Rowing'), ('skierg','NordicSki'), - ('Bike','Ride'), + ('bike','Ride'), ('bikeerg','Ride'), ('dynamic','Rowing'), ('slides','Rowing'), @@ -84,7 +84,7 @@ garmincollection = ( ('water','ROWING'), ('rower','INDOOR_ROWING'), ('skierg','CROSS_COUNTRY_SKIING'), - ('Bike','ROAD_BIKING'), + ('bike','ROAD_BIKING'), ('bikeerg','INDOOR_CYCLING'), ('dynamic','INDOOR_ROWING'), ('slides','INDOOR_ROWING'), @@ -117,7 +117,7 @@ fitcollection = ( ('water','rowing'), ('rower','rowing'), ('skierg','cross_country_skiing'), -('Bike','cycling'), +('bike','cycling'), ('bikeerg','cycling'), ('dynamic','rowing'), ('slides','rowing'), @@ -153,7 +153,7 @@ stcollection = ( ('water','Rowing'), ('rower','Rowing'), ('skierg','Skiing:Nordic'), - ('Bike','Cycling'), + ('bike','Cycling'), ('bikeerg','Cycling'), ('dynamic','Rowing'), ('slides','Rowing'), @@ -187,7 +187,7 @@ rkcollection = ( ('water','Rowing'), ('rower','Rowing'), ('skierg','Cross-Country Skiing'), - ('Bike','Cycling'), + ('bike','Cycling'), ('bikeerg','Cycling'), ('dynamic','Rowing'), ('slides','Rowing'), @@ -220,7 +220,7 @@ polarcollection = ( ('water','Rowing'), ('rower','Rowing'), ('skierg','Skiing'), - ('Bike','Cycling'), + ('bike','Cycling'), ('bikeerg','Cycling'), ('dynamic','Rowing'), ('slides','Rowing'), @@ -253,7 +253,7 @@ tpcollection = ( ('water','rowing'), ('rower','rowing'), ('skierg','xc-ski'), - ('Bike','bike'), + ('bike','bike'), ('Bikeerg','bike'), ('dynamic','rowing'), ('slides','rowing'), @@ -286,7 +286,7 @@ c2collection = ( ('water','water'), ('rower','rower'), ('skierg','skierg'), - ('Bike','bike'), + ('bike','bike'), ('bikeerg','bike'), ('dynamic','dynamic'), ('slides','slides'), diff --git a/rowers/tests/test_emails.py b/rowers/tests/test_emails.py index 8b8bf8ca..ea6fdfec 100644 --- a/rowers/tests/test_emails.py +++ b/rowers/tests/test_emails.py @@ -427,7 +427,7 @@ workout bike self.assertEqual(len(ws),1) w = ws[0] - self.assertEqual(w.workouttype,'Bike') + self.assertEqual(w.workouttype,'bike') @@ -575,7 +575,7 @@ race 1 self.assertIn('Successfully processed email attachments',out.getvalue()) ws = Workout.objects.filter(name="johnsworkout") - + self.assertEqual(len(ws),1) w = ws[0] diff --git a/rowers/utils.py b/rowers/utils.py index 4594c9ad..c3cfbeff 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -524,7 +524,6 @@ def get_strava_stream(r,metric,stravaid,series_type='time',fetchresolution='high if metric=='power': # pragma: no cover with open('data.txt', 'w') as outfile: json.dump(s.json(), outfile) - print('saved to file') for data in s.json(): y = None From d50ba322b8bb4f452c0dc3cf86aa3eb7a9025efa Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 10 May 2021 20:47:10 +0200 Subject: [PATCH 05/12] saving intermediate state --- rowers/utils.py | 50 ++++++++++++++++++++++++++++++++++++ rowers/views/workoutviews.py | 40 +++++++++++++++++------------ 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/rowers/utils.py b/rowers/utils.py index c3cfbeff..4330767b 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -1164,3 +1164,53 @@ def request_is_ajax(request): # is_ajax = True return is_ajax + +def intervals_to_string(vals, units, typ): + if vals is None or units is None or typ is None: + return '' + if len(vals) != len(units) or len(vals) != len(typ): + return '' + + s = '' + previous = 'rest' + for i in range(len(vals)): + if typ[i] == 'rest' and previous == 'rest': + if units[i] == 'min': + val = int(vals[i])*60 + unit = 'sec' + else: + val = int(vals[i]) + if units[i] == 'meters': + unit = 'm' + if units[i] == 'seconds': + unit = 'sec' + s += '+0min/{val}{unit}'.format(val=val,unit=unit) + elif typ[i] == 'rest': + if units[i] == 'min': + val = int(vals[i])*60 + unit = 'sec' + else: + val = int(vals[i]) + if units[i] == 'meters': + unit = 'm' + if units[i] == 'seconds': + unit = 'sec' + s += '/{val}{unit}'.format(val=val,unit=unit) + previous = 'rest' + else: # work interval + if units[i] == 'min': + val = int(vals[i])*60 + unit = 'sec' + else: + val = int(vals[i]) + if units[i] == 'meters': + unit = 'm' + if units[i] == 'seconds': + unit = 'sec' + s += '+{val}{unit}'.format(val=val,unit=unit) + previous = 'work' + + if s[0] == '+': + s = s[1:] + + return s diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index a273601f..1227ff68 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -13,6 +13,7 @@ import numpy from rowers.mailprocessing import send_confirm import rowers.uploads as uploads import rowers.utils as utils +from rowers.utils import intervals_to_string from urllib.parse import urlparse, parse_qs from json.decoder import JSONDecodeError @@ -2297,7 +2298,7 @@ def workout_view(request,id=0,raceresult=0,sessionresult=0,nocourseraceresult=0) intervaldata['itime'] = itime intervaldata['itype'] = itype - rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger', + vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger', debug=False,smoothwindow=15., activewindow = [startsecond,endsecond]) summary = rowdata.allstats() @@ -2316,7 +2317,7 @@ def workout_view(request,id=0,raceresult=0,sessionresult=0,nocourseraceresult=0) intervaldata['itime'] = itime intervaldata['itype'] = itype - rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger', + vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger', debug=False,smoothwindow=15., activewindow = [startsecond,endsecond]) summary = rowdata.allstats() @@ -2336,7 +2337,7 @@ def workout_view(request,id=0,raceresult=0,sessionresult=0,nocourseraceresult=0) intervaldata['itime'] = itime intervaldata['itype'] = itype - rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger', + vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger', debug=False,smoothwindow=15., activewindow = [startsecond,endsecond]) summary = rowdata.allstats() @@ -6240,7 +6241,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" if powerorpace == 'power' and power is not None: try: - rowdata.updateinterval_metric( + vals, units, typ = rowdata.updateinterval_metric( ' Power (watts)',power,mode='larger', debug=False,smoothwindow=15., activewindow=[activesecondsmin,activesecondsmax], @@ -6250,7 +6251,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" elif powerorpace == 'pace': # pragma: no cover try: velo = 500./pace_secs - rowdata.updateinterval_metric( + vals, units, typ = rowdata.updateinterval_metric( ' AverageBoatSpeed (m/s)',velo,mode='larger', debug=False,smoothwindow=15., activewindow=[activesecondsmin,activesecondsmax], @@ -6259,7 +6260,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" messages.error(request,'Error updating pace') elif powerorpace == 'work': # pragma: no cover try: - rowdata.updateinterval_metric( + vals, units, typ = rowdata.updateinterval_metric( 'driveenergy',work,mode='larger', debug=False,smoothwindow=15., activewindow=[activesecondsmin,activesecondsmax], @@ -6268,13 +6269,17 @@ def workout_summary_edit_view(request,id,message="",successmessage="" messages.error(request,'Error updating Work per Stroke') elif powerorpace == 'spm': # pragma: no cover try: - rowdata.updateinterval_metric( + vals, units, typ = rowdata.updateinterval_metric( ' Cadence (stokes/min)',spm,mode='larger', debug=False,smoothwindow=2., activewindow=[activesecondsmin,activesecondsmax],) except: messages.error(request,'Error updating SPM') + intervalString = '' + if vals is not None: + intervalString = intervals_to_string(vals, units, typ) + intervalstats = rowdata.allstats() itime,idist,itype = rowdata.intervalstats_values() nrintervals = len(idist) @@ -6296,7 +6301,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'activeminutesmin': activeminutesmin, 'activeminutesmax': activeminutesmax, } - form = SummaryStringForm() + form = SummaryStringForm(initial={'intervalstring':intervalString}) powerupdateform = PowerIntervalUpdateForm(initial=data) savebutton = 'savepowerpaceform' formvalues = { @@ -6368,7 +6373,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" pace_secs = 120. if powerorpace == 'power' and power is not None: - rowdata.updateinterval_metric(' Power (watts)',power,mode='larger', + vals, units, typ = rowdata.updateinterval_metric(' Power (watts)',power,mode='larger', debug=False,smoothwindow=15, activewindow=[activesecondsmin,activesecondsmax], ) @@ -6376,7 +6381,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" elif powerorpace == 'pace': # pragma: no cover try: velo = 500./pace_secs - rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',velo,mode='larger', + vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',velo,mode='larger', debug=False,smoothwindow=15, activewindow=[activesecondsmin,activesecondsmax], ) @@ -6384,7 +6389,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" messages.error(request,'Error updating pace') elif powerorpace == 'work': # pragma: no cover try: - rowdata.updateinterval_metric( + vals, units, typ = rowdata.updateinterval_metric( 'driveenergy',work,mode='larger', debug=False,smoothwindow=15., activewindow=[activesecondsmin,activesecondsmax],) @@ -6392,7 +6397,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" messages.error(request,'Error updating Work per Stroke') elif powerorpace == 'spm': # pragma: no cover try: - rowdata.updateinterval_metric(' Cadence (stokes/min)',spm,mode='larger', + vals, units, typ = rowdata.updateinterval_metric(' Cadence (stokes/min)',spm,mode='larger', debug=False,smoothwindow=2., activewindow=[activesecondsmin,activesecondsmax], ) @@ -6400,6 +6405,11 @@ def workout_summary_edit_view(request,id,message="",successmessage="" messages.error(request,'Error updating SPM') + intervalString = '' + if vals is not None: + intervalString = intervals_to_string(vals, units, typ) + + intervalstats = rowdata.allstats() itime,idist,itype = rowdata.intervalstats_values() nrintervals = len(idist) @@ -6414,11 +6424,10 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'activeminutesmax': activeminutesmax, } powerupdateform = PowerIntervalUpdateForm(initial=cd) - form = SummaryStringForm() + form = SummaryStringForm(initial={'intervalstring':intervalString}) + - form = SummaryStringForm() - # we are saving the results obtained from the detailed form elif request.method == 'POST' and "savedetailform" in request.POST: # pragma: no cover savebutton = 'savedetailform' @@ -6562,7 +6571,6 @@ def workout_summary_edit_view(request,id,message="",successmessage="" div = '' # render page - return render(request, 'summary_edit.html', {'form':form, 'activeminutesmax':activeminutesmax, From 105a78d392b46c2537b24169b796f8cc01de2aa8 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 10 May 2021 20:47:19 +0200 Subject: [PATCH 06/12] removing detail form --- rowers/templates/summary_edit.html | 35 -------- rowers/views/workoutviews.py | 125 ----------------------------- 2 files changed, 160 deletions(-) diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index 422f34bc..fb021a59 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -111,9 +111,6 @@ {% for key,value in formvalues.items %} {% endfor %} - {% for field in detailform %} - {{ field.as_hidden }} - {% endfor %}

@@ -130,38 +127,6 @@

  • -

    Detailed Summary Edit

    -

    This is still experimental and there are known bugs. Use at your own risk. Nothing is stored permanently until you hit Save in the Updated Summary section. You can use the restore original button to restore the original values.

    -
    - - - - - - - - {% for i in nrintervals|times %} - - - - - - - {% endfor %} - -
    #TimeDistanceType
    {{ i }}  - {% get_field_id i "intervalt_" detailform %} - - {% get_field_id i "intervald_" detailform %} - - {% get_field_id i "type_" detailform %} -
    - {% csrf_token %} - - -
    -
  • -
  • Interval Shorthand How-To

    This is a quick way to enter the intervals using a special mini-language.

    You enter something like 8x500m/3min, press "Update" and the site will interpret this for you and update the summary on the right. If you're happy with the result, press the green Save button to update the values. Nothing will be changed permanently until you hit Save.

    diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 1227ff68..1f46d518 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -6425,133 +6425,9 @@ def workout_summary_edit_view(request,id,message="",successmessage="" } powerupdateform = PowerIntervalUpdateForm(initial=cd) form = SummaryStringForm(initial={'intervalstring':intervalString}) - - - - # we are saving the results obtained from the detailed form - elif request.method == 'POST' and "savedetailform" in request.POST: # pragma: no cover - savebutton = 'savedetailform' - form = SummaryStringForm() - nrintervals = int(request.POST['nrintervals']) - detailform = IntervalUpdateForm(request.POST,aantal=nrintervals) - itime = [] - idist = [] - itype = [] - ivalues = [] - iunits = [] - itypes = [] - iresults = [] - for i in range(nrintervals): - try: - t = datetime.datetime.strptime(request.POST['intervalt_%s' % i],"%H:%M:%S.%f") - except ValueError: - t = datetime.datetime.strptime(request.POST['intervalt_%s' % i],"%H:%M:%S") - - timesecs = 3600*t.hour+60*t.minute+t.second+t.microsecond/1.e6 - itime += [timesecs] - idist += [int(request.POST['intervald_%s' % i])] - itype += [int(request.POST['type_%s' % i])] - - if itype[i] == 3: # rest - itypes += ['rest'] - ivalues += [timesecs] - iresults += [idist[i]] - iunits += ['seconds'] - if itype[i] == 5 or itype[i] == 2: # distance based work - itypes += ['work'] - ivalues += [idist[i]] - iresults += [timesecs] - iunits += ['meters'] - if itype[i] == 4 or itype[i] == 1: # time based work - itypes += ['work'] - ivalues += [timesecs] - iresults += [idist[i]] - iunits += ['seconds'] - - - rowdata.updateintervaldata(ivalues,iunits,itypes,iresults=iresults) - intervalstats = rowdata.allstats() - row.summary = intervalstats - try: - row.notes += "\n"+s - except TypeError: - pass - - row.save() - rowdata.write_csv(f1,gzip=True) - dataprep.update_strokedata(encoder.decode_hex(id),rowdata.df) - messages.info(request,"Updated interval data saved") - - form = SummaryStringForm() - powerupdateform = PowerIntervalUpdateForm(initial={ - 'power': int(normp), - 'pace': avpace, - 'selector': 'power', - 'work': int(normw), - 'spm': int(normspm), - 'activeminutesmin': 0, - 'activeminutesmax': activeminutesmax, - }) - - - # we are processing the details form - elif request.method == 'POST' and "nrintervals" in request.POST: # pragma: no cover - savebutton = 'savedetailform' - nrintervals = int(request.POST['nrintervals']) - detailform = IntervalUpdateForm(request.POST,aantal=nrintervals) - if detailform.is_valid(): - cd = detailform.cleaned_data - itime = [] - idist = [] - itype = [] - ivalues = [] - iunits = [] - itypes = [] - iresults = [] - for i in range(nrintervals): - t = cd['intervalt_%s' % i] - timesecs = t.total_seconds() - itime += [timesecs] - idist += [cd['intervald_%s' % i]] - itype += [cd['type_%s' % i]] - - if itype[i] == '3': # rest - itypes += ['rest'] - ivalues += [timesecs] - iresults += [idist[i]] - iunits += ['seconds'] - if itype[i] == '5' or itype[i] == '2': # distance based work - itypes += ['work'] - ivalues += [idist[i]] - iresults += [timesecs] - iunits += ['meters'] - if itype[i] == '4' or itype[i] == '1': # time based work - itypes += ['work'] - ivalues += [timesecs] - iresults += [idist[i]] - iunits += ['seconds'] - - rowdata.updateintervaldata(ivalues,iunits, - itypes,iresults=iresults) - intervalstats = rowdata.allstats() - - - form = SummaryStringForm() - powerupdateform = PowerIntervalUpdateForm() - initial = {} - for i in range(nrintervals): - try: - initial['intervald_%s' % i] = idist[i] - initial['intervalt_%s' % i] = get_time(itime[i]) - initial['type_%s' % i] = itype[i] - except IndexError: # pragma: no cover - pass - - - detailform = IntervalUpdateForm(aantal=nrintervals,initial=initial) # create interactive plot try: @@ -6576,7 +6452,6 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'activeminutesmax':activeminutesmax, 'activeminutesmin':activeminutesmin, 'maxminutes': maxminutes, - 'detailform':detailform, 'powerupdateform':powerupdateform, 'workout':row, 'rower':r, From 06c50cedfe516a46f3b5685aa625f251398c7d06 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 10 May 2021 21:16:07 +0200 Subject: [PATCH 07/12] recognizing images / json --- rowers/dataprep.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 66e63a01..afe94e76 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -2013,6 +2013,16 @@ def new_workout_from_file(r, f2, return -1, message, f2 # Some people try to upload Concept2 logbook summaries + if fileformat == 'imageformat': + os.remove(f2) + message = "You cannot upload image files here" + return (0, message, f2) + + if fileformat == 'json': + os.remove(f2) + message = "JSON format not supported in direct upload" + return (0, message, f2) + if fileformat == 'c2log': os.remove(f2) message = "This summary does not contain stroke data. Use the files containing stroke by stroke data." From f03e31e74b33d40c7c4e86bfb2bd6863f40eb1e0 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 10 May 2021 21:34:32 +0200 Subject: [PATCH 08/12] recognizing img files --- rowers/rows.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rowers/rows.py b/rowers/rows.py index b5b8d854..8f1c3643 100644 --- a/rowers/rows.py +++ b/rowers/rows.py @@ -68,7 +68,8 @@ def validate_file_extension(value): ext = os.path.splitext(value.name)[1] valid_extensions = ['.tcx','.csv','.TCX','.gpx','.GPX', '.CSV','.fit','.FIT','.zip','.ZIP', - '.gz','.GZ','.xls'] + '.gz','.GZ','.xls', + '.jpg','.jpeg','.tiff','.png','.gif','.bmp'] if not ext in valid_extensions: # pragma: no cover raise ValidationError(u'File not supported!') From 2235b28cd589bc05bd1728a2190354f57d4e0474 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 11 May 2021 18:19:22 +0200 Subject: [PATCH 09/12] c2 stuff now using timezone info --- rowers/c2stuff.py | 27 ++++++++++++++++----------- rowers/dataprep.py | 4 ++++ rowers/stravastuff.py | 2 +- rowers/tests/mocks.py | 8 +++++++- rowers/tests/test_imports.py | 15 +++++++++++++++ rowers/views/importviews.py | 1 + 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index 496cc8d7..4c9f6506 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -1076,20 +1076,21 @@ def add_workout_from_data(user,importid,data,strokedata, except: # pragma: no cover comments = ' ' - try: - thetimezone = tz(data['timezone']) - except: - thetimezone = 'UTC' + + thetimezone = pytz.timezone(data['timezone']) + r = Rower.objects.get(user=user) try: rowdatetime = iso8601.parse_date(data['date_utc']) + thetimezone = 'UTC' except KeyError: # pragma: no cover rowdatetime = iso8601.parse_date(data['start_date']) + rowdatetime = rowdatetime.make_aware(thetimezone) except ParseError: # pragma: no cover rowdatetime = iso8601.parse_date(data['date']) - + rowdatetime = rowdatetime.make_aware(thetimezone) try: @@ -1119,7 +1120,9 @@ def add_workout_from_data(user,importid,data,strokedata, cum_time = res[0] lapidx = res[1] - starttimeunix = starttimeunix - cum_time.max() + totaltime = data['time']/10. + starttimeunix = starttimeunix - totaltime + unixtime = cum_time+starttimeunix # unixtime[0] = starttimeunix @@ -1161,7 +1164,6 @@ def add_workout_from_data(user,importid,data,strokedata, velo = 1000./pace pace = 500./velo - # save csv # Create data frame with all necessary data to write to csv df = pd.DataFrame({'TimeStamp (sec)':unixtime, @@ -1223,15 +1225,18 @@ def add_workout_from_data(user,importid,data,strokedata, w = Workout.objects.get(id=id) + local_tz = pytz.timezone(data['timezone']) +# local_tz = pytz.timezone(thetimezone) + + w.startdatetime = w.startdatetime.astimezone(local_tz) + w.starttime = w.startdatetime.strftime('%H:%M:%S') + w.timezone = local_tz w.duration = dataprep.totaltime_sec_to_string(totaltime) w.distance = totaldist - w.startdatetime = rowdatetime - w.starttime = rowdatetime.time() - w.date = rowdatetime.date() + w.save() - return id,message diff --git a/rowers/dataprep.py b/rowers/dataprep.py index afe94e76..f55ff83e 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1692,6 +1692,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', + try: latavg = row.df[' latitude'].mean() lonavg = row.df[' longitude'].mean() @@ -1717,6 +1718,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', except KeyError: timezone_str = r.defaulttimezone + duration = totaltime_sec_to_string(totaltime) workoutdate = workoutstartdatetime.astimezone( @@ -1785,6 +1787,8 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', except ValidationError: return (0,'Unable to create your workout') + + if privacy == 'visible': ts = Team.objects.filter(rower=r) for t in ts: diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py index 8e9b7ebc..93d865ae 100644 --- a/rowers/stravastuff.py +++ b/rowers/stravastuff.py @@ -330,7 +330,7 @@ def create_async_workout(alldata,user,stravaid,debug=False): starttime = rowdatetime.astimezone( pytz.timezone(thetimezone) - ).strftime('%H:%m:%S') + ).strftime('%H:%M:%S') totaltime = data['elapsed_time'] duration = dataprep.totaltime_sec_to_string(totaltime) diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 8c0d1f51..891aa3ac 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -713,6 +713,9 @@ def mocked_requests(*args, **kwargs): with open('rowers/tests/testdata/c2jsonworkoutdata.txt','r') as infile: c2workoutdata = json.load(infile) + with open('rowers/tests/testdata/c2_timezone.json','r') as infile: + c2timezoneworkoutdata = json.load(infile) + with open('rowers/tests/testdata/c2jsonstrokedata.txt','r') as infile: c2strokedata = json.load(infile) @@ -1181,7 +1184,10 @@ def mocked_requests(*args, **kwargs): if c2strokestester.match(args[0]): return MockResponse(c2strokedata,200) elif c2importtester.match(args[0]): - return MockResponse(c2workoutdata,200) + if '12' in args[0]: + return MockResponse(c2workoutdata,200) + else: + return MockResponse(c2timezoneworkoutdata,200) elif c2workoutlisttester.match(args[0]): return MockResponse(c2workoutlist,200) elif 'access_token' in args[0]: diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index 233ad051..d9ddabc0 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -261,6 +261,21 @@ class C2Objects(DjangoTestCase): self.assertEqual(response.status_code, 200) + @patch('rowers.c2stuff.requests.get', side_effect=mocked_requests) + @patch('rowers.dataprep.create_engine') + def test_c2_import_tz(self, mock_get, mocked_sqlalchemy): + + response = self.c.get('/rowers/workout/c2import/22/',follow=True) + + self.assertRedirects(response, + expected_url='/rowers/workout/'+encoded2+'/edit/', + status_code=302,target_status_code=200) + + self.assertEqual(response.status_code, 200) + + w = Workout.objects.get(id=2) + self.assertEqual(w.timezone,'Europe/Prague') + @patch('rowers.dataprep.create_engine') def test_strokedata(self, mocked_sqlalchemy): diff --git a/rowers/views/importviews.py b/rowers/views/importviews.py index 1993490b..08922a89 100644 --- a/rowers/views/importviews.py +++ b/rowers/views/importviews.py @@ -2099,6 +2099,7 @@ def workout_getimportview(request,externalid,source = 'c2',do_async=False): + workoutdate = startdatetime.astimezone( pytz.timezone(timezone_str) ).strftime('%Y-%m-%d') From 4afaa5fd10519a23fdf87f6156ef88a2a6d58e57 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 12 May 2021 06:52:53 +0200 Subject: [PATCH 10/12] testing c2 import --- rowers/c2stuff.py | 3 +++ rowers/dataprep.py | 4 ++-- rowers/forms.py | 26 -------------------------- rowers/templatetags/rowerfilters.py | 4 ++-- rowers/tests/mocks.py | 6 ++++++ rowers/tests/test_imports.py | 18 ++++++++++++++++++ rowers/utils.py | 14 +++++++------- rowers/views/statements.py | 4 ++-- 8 files changed, 40 insertions(+), 39 deletions(-) diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index 4c9f6506..503dd850 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -848,6 +848,9 @@ def get_workout(user,c2id,do_async=False): data = s.json()['data'] splitdata = None + #with open('c2temp.json','w') as f: + # f.write(json.dumps(s.json())) + # print(s.json()) if 'workout' in data: if 'splits' in data['workout']: # pragma: no cover diff --git a/rowers/dataprep.py b/rowers/dataprep.py index f55ff83e..2711a35d 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -2017,12 +2017,12 @@ def new_workout_from_file(r, f2, return -1, message, f2 # Some people try to upload Concept2 logbook summaries - if fileformat == 'imageformat': + if fileformat == 'imageformat': # pragma: no cover os.remove(f2) message = "You cannot upload image files here" return (0, message, f2) - if fileformat == 'json': + if fileformat == 'json': # pragma: no cover os.remove(f2) message = "JSON format not supported in direct upload" return (0, message, f2) diff --git a/rowers/forms.py b/rowers/forms.py index c1957690..76d00219 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -994,32 +994,6 @@ class PowerIntervalUpdateForm(forms.Form): activeminutesmin = forms.IntegerField(required=False,initial=0,widget=forms.HiddenInput()) activeminutesmax = forms.IntegerField(required=False,initial=0,widget=forms.HiddenInput()) -# Form used to update interval stats -class IntervalUpdateForm(forms.Form): - - def __init__(self, *args, **kwargs): - typechoices = ( - (1,'single time'), - (2,'single distance'), - (3,'rest (time based)'), - (3,'rest (distance based)'), - (4,'work (time based)'), - (5,'work (distance based)'), - ) - aantal = int(kwargs.pop('aantal')) - super(IntervalUpdateForm, self).__init__(*args, **kwargs) - - for i in range(aantal): - self.fields['intervalt_%s' % i] = forms.DurationField(label='Time '+str(i+1)) - self.fields['intervald_%s' % i] = forms.IntegerField(label='Distance '+str(i+1)) - self.fields['type_%s' % i] = forms.ChoiceField(choices=typechoices, - required=True, - initial=4, - label = 'Type '+str(i+1)) - self.fields['intervalt_%s' % i].widget.attrs['style'] = 'width:76px; height: 16px;' - self.fields['intervald_%s' % i].widget.attrs['style'] = 'width:76px; height: 16px;' - self.fields['type_%s' % i].widget.attrs['style'] = 'width:156px; height: 22px;' - self.fields['intervald_%s' % i].widget = forms.TimeInput(format='%H:%M:%S.%f') boattypes = mytypes.boattypes workouttypes = mytypes.workouttypes diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index 3c7a1624..2ee4bf9d 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -559,7 +559,7 @@ def iterrows(df): # pragma: no cover return df.iterrows() @register.filter(name='times') -def times(number): +def times(number): # pragma: no cover return range(number) @register.simple_tag @@ -567,7 +567,7 @@ def get_df_iloc(data,i,j): # pragma: no cover return data.iloc(i,j) @register.simple_tag -def get_field_id(id,s,form): +def get_field_id(id,s,form): # pragma: no cover field_name = s+str(id) return form.__getitem__(field_name) diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 891aa3ac..66ba56bf 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -716,6 +716,10 @@ def mocked_requests(*args, **kwargs): with open('rowers/tests/testdata/c2_timezone.json','r') as infile: c2timezoneworkoutdata = json.load(infile) + with open('rowers/tests/testdata/c2_timezone2.json','r') as infile: + c2timezoneworkoutdata2 = json.load(infile) + + with open('rowers/tests/testdata/c2jsonstrokedata.txt','r') as infile: c2strokedata = json.load(infile) @@ -1186,6 +1190,8 @@ def mocked_requests(*args, **kwargs): elif c2importtester.match(args[0]): if '12' in args[0]: return MockResponse(c2workoutdata,200) + elif '31' in args[0]: + return MockResponse(c2timezoneworkoutdata2,200) else: return MockResponse(c2timezoneworkoutdata,200) elif c2workoutlisttester.match(args[0]): diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index d9ddabc0..cd4640a3 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -276,6 +276,24 @@ class C2Objects(DjangoTestCase): w = Workout.objects.get(id=2) self.assertEqual(w.timezone,'Europe/Prague') + @patch('rowers.c2stuff.requests.get', side_effect=mocked_requests) + @patch('rowers.dataprep.create_engine') + def test_c2_import_tz2(self, mock_get, mocked_sqlalchemy): + + response = self.c.get('/rowers/workout/c2import/31/',follow=True) + + self.assertRedirects(response, + expected_url='/rowers/workout/'+encoded2+'/edit/', + status_code=302,target_status_code=200) + + self.assertEqual(response.status_code, 200) + + w = Workout.objects.get(id=2) + self.assertEqual(w.timezone,'Europe/Amsterdam') + + self.assertEqual(w.starttime.strftime("%H:%M:%S"),"20:04:56") + + @patch('rowers.dataprep.create_engine') def test_strokedata(self, mocked_sqlalchemy): diff --git a/rowers/utils.py b/rowers/utils.py index 4330767b..defdd0af 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -699,7 +699,7 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7): rscore = 100.*(avgpower/ftp)*seconds/3600. - if targettype == 'Cadence': + if targettype == 'Cadence': # pragma: no cover value = step.get('targetValue',0) valuelow = step.get('targetValueLow',0) valuehigh = step.get('targetValueHigh',0) @@ -1166,26 +1166,26 @@ def request_is_ajax(request): return is_ajax def intervals_to_string(vals, units, typ): - if vals is None or units is None or typ is None: + if vals is None or units is None or typ is None: # pragma: no cover return '' - if len(vals) != len(units) or len(vals) != len(typ): + if len(vals) != len(units) or len(vals) != len(typ): # pragma: no cover return '' s = '' previous = 'rest' for i in range(len(vals)): if typ[i] == 'rest' and previous == 'rest': - if units[i] == 'min': + if units[i] == 'min': # pragma: no cover val = int(vals[i])*60 unit = 'sec' else: val = int(vals[i]) - if units[i] == 'meters': + if units[i] == 'meters': # pragma: no cover unit = 'm' if units[i] == 'seconds': unit = 'sec' s += '+0min/{val}{unit}'.format(val=val,unit=unit) - elif typ[i] == 'rest': + elif typ[i] == 'rest': # pragma: no cover if units[i] == 'min': val = int(vals[i])*60 unit = 'sec' @@ -1197,7 +1197,7 @@ def intervals_to_string(vals, units, typ): unit = 'sec' s += '/{val}{unit}'.format(val=val,unit=unit) previous = 'rest' - else: # work interval + else: # pragma: no cover # work interval if units[i] == 'min': val = int(vals[i])*60 unit = 'sec' diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 67f62298..b74f1616 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -91,7 +91,7 @@ from django.utils.datastructures import MultiValueDictKeyError from django.utils import timezone,translation from django.core.mail import send_mail, BadHeaderError from rowers.forms import ( - SummaryStringForm,IntervalUpdateForm,StrokeDataForm, + SummaryStringForm,StrokeDataForm, StatsOptionsForm,PredictedPieceForm,DateRangeForm,DeltaDaysForm, FitnessMetricForm,PredictedPieceFormNoDistance, EmailForm, RegistrationForm, RegistrationFormTermsOfService, @@ -1161,7 +1161,7 @@ def get_my_teams(user): return teams # Used for the interval editor - translates seconds to a time object -def get_time(second): +def get_time(second): # pragma: no cover if (second<=0) or (second>1e9): hours = 0 minutes=0 From 4db0c20efa3dfb6c74cac332b1eeea6c5c4be8f5 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 12 May 2021 08:52:51 +0200 Subject: [PATCH 11/12] further timezone magic c2 --- rowers/c2stuff.py | 2 +- rowers/tasks.py | 31 +++++++++++++++---------------- rowers/tests/test_imports.py | 2 ++ rowers/views/workoutviews.py | 11 +++++++++-- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index 503dd850..d7544dfc 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -238,7 +238,7 @@ def create_async_workout(alldata,user,c2id): duration = dataprep.totaltime_sec_to_string(totaltime) try: - timezone_str = tz(data['timezone']) + timezone_str = data['timezone'] except: timezone_str = 'UTC' diff --git a/rowers/tasks.py b/rowers/tasks.py index fdaf22fe..9bfb8d28 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -419,7 +419,7 @@ def handle_c2_import_stroke_data(c2token, duration = datetime.datetime.strptime(duration,'%H:%M:%S.%f').time() try: - timezone_str = tz(workoutdata['timezone']) + timezone_str = workoutdata['timezone'] except: timezone_str = 'UTC' @@ -2991,7 +2991,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone c2id = data['id'] workouttype = data['type'] verified = data['verified'] - startdatetime = iso8601.parse_date(data['date']) + startdatetime = iso8601.parse_date(data['date_utc']) weightclass = data['weight_class'] try: @@ -3013,21 +3013,18 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone totaltime = data['time']/10. duration = totaltime_sec_to_string(totaltime) + starttimeunix = arrow.get(startdatetime).timestamp()-totaltime + startdatetime = arrow.get(starttimeunix) - try: - timezone_str = tz(data['timezone']) - except: - timezone_str = defaulttimezone + timezone = pytz.timezone(data['timezone']) + startdatetime = startdatetime.astimezone(timezone) - startdatetime = startdatetime.replace(tzinfo=None) - tz = pytz.timezone(timezone_str) - startdatetime = tz.localize(startdatetime) workoutdate = startdatetime.astimezone( - pytz.timezone(timezone_str) + timezone ).strftime('%Y-%m-%d') starttime = startdatetime.astimezone( - pytz.timezone(timezone_str) + timezone ).strftime('%H:%M:%S') try: @@ -3056,8 +3053,6 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone cum_time = res[0] lapidx = res[1] - starttimeunix = arrow.get(startdatetime).timestamp() - starttimeunix = starttimeunix-cum_time.max() unixtime = cum_time+starttimeunix # unixtime[0] = starttimeunix @@ -3134,6 +3129,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone 'workouttype':workouttype, 'boattype':'1x', 'c2id':c2id, + 'timezone':str(timezone) } session = requests.session() @@ -3165,8 +3161,11 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone parkedids = [] with open('c2blocked.json','r') as c2blocked: - jsondata = json.load(c2blocked) - parkedids = jsondata['ids'] + try: + jsondata = json.load(c2blocked) + parkedids = jsondata['ids'] + except JSONDecodeError: + parkedids = [] newparkedids = [id for id in parkedids if id != newc2id] with open('c2blocked.json','wt') as c2blocked: @@ -3364,7 +3363,7 @@ def fetch_strava_workout(stravatoken,oauth_data,stravaid,csvfilename,userid,debu comments = ' ' try: - thetimezone = tz(workoutsummary['timezone']) + thetimezone = workoutsummary['timezone'] except: thetimezone = 'UTC' diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index cd4640a3..bfef0eee 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -158,6 +158,7 @@ class C2Objects(DjangoTestCase): self.r.c2token = '12' self.r.c2refreshtoken = 'ab' self.r.tokenexpirydate = arrow.get(datetime.datetime.now()+datetime.timedelta(days=1)).datetime + self.r.defaulttimezone = 'Europe/Prague' self.r.save() self.c.login(username='john',password='koeinsloot') @@ -342,6 +343,7 @@ class C2Objects(DjangoTestCase): res = tasks.handle_c2_async_workout(alldata,self.u.id,self.r.c2token,33991243,0,self.r.defaulttimezone) self.assertEqual(res,1) + @override_settings(TESTING=True) class C2ObjectsTokenExpired(DjangoTestCase): def setUp(self): diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 1f46d518..0767310c 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -4813,8 +4813,8 @@ def workout_upload_api(request): totalDistance = post_data.get('totalDistance',None) elapsedTime = post_data.get('elapsedTime',None) summary = post_data.get('summary',None) - - + timezone = post_data.get('timezone',None) + r = None if form.is_valid(): t = form.cleaned_data['title'] @@ -4905,6 +4905,13 @@ def workout_upload_api(request): return JSONResponse(status=200,data=message) w = Workout.objects.get(id=id) + if timezone is not None: + w.startdatetime = w.startdatetime.astimezone(pytz.timezone(timezone)) + w.workoutdate = w.startdatetime.strftime('%Y-%m-%d') + w.starttime = w.starttime.strftime('%H:%M:%S') + w.timezone = timezone + w.save() + if make_plot: # pragma: no cover From ae87acc14b52f7765f654d915c8e8f0f925c41db Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 12 May 2021 09:28:00 +0200 Subject: [PATCH 12/12] finishing c2 sync --- rowers/c2stuff.py | 2 +- rowers/tasks.py | 2 +- rowers/views/workoutviews.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index d7544dfc..28084073 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -239,7 +239,7 @@ def create_async_workout(alldata,user,c2id): try: timezone_str = data['timezone'] - except: + except: # pragma: no cover timezone_str = 'UTC' workoutdate = startdatetime.astimezone( diff --git a/rowers/tasks.py b/rowers/tasks.py index 9bfb8d28..b4d9b3b2 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -3164,7 +3164,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone try: jsondata = json.load(c2blocked) parkedids = jsondata['ids'] - except JSONDecodeError: + except JSONDecodeError: # pragma: no cover parkedids = [] newparkedids = [id for id in parkedids if id != newc2id] diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 0767310c..ec44ea1a 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -4814,7 +4814,7 @@ def workout_upload_api(request): elapsedTime = post_data.get('elapsedTime',None) summary = post_data.get('summary',None) timezone = post_data.get('timezone',None) - + r = None if form.is_valid(): t = form.cleaned_data['title'] @@ -4905,7 +4905,7 @@ def workout_upload_api(request): return JSONResponse(status=200,data=message) w = Workout.objects.get(id=id) - if timezone is not None: + if timezone is not None: # pragma: no cover w.startdatetime = w.startdatetime.astimezone(pytz.timezone(timezone)) w.workoutdate = w.startdatetime.strftime('%Y-%m-%d') w.starttime = w.starttime.strftime('%H:%M:%S')