From 54190ed383488be83ad0b163d658daed823fdde7 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 16 May 2021 07:30:56 +0200 Subject: [PATCH 1/9] works except for target cadence --- garminscript.py | 115 ++++++++++++++++++++++++--- rowers/garmin_stuff.py | 174 ++++++++++++++++++++++++++++------------- rowers/models.py | 2 + 3 files changed, 225 insertions(+), 66 deletions(-) diff --git a/garminscript.py b/garminscript.py index b288cd1e..9cb803af 100644 --- a/garminscript.py +++ b/garminscript.py @@ -28,23 +28,24 @@ payload = { 'workoutSourceId': 'Rowsandall.com', 'steps': [ { - 'type': 'WorkoutStep', 'stepOrder': 0, + 'type': 'WorkoutStep', + 'stepOrder': 0, #'repeatType': 'Step', - 'repeatValue': 1, + #'repeatValue': 1, 'intensity': 'ACTIVE', - 'description': '0', + 'description': 'At 220W', 'durationType': 'TIME', 'durationValue': 1800, - 'durationValueType': 'METER', + 'durationValueType': None, 'targetType': 'POWER', - 'targetValue': 1226, + 'targetValue': 226, 'targetValueLow': None, 'targetValueHigh': None, } ] } -payload = { +payload2 = { "workoutName": "Test", "description": "Test", "sport": "CYCLING", @@ -55,16 +56,93 @@ payload = { "intensity": "INTERVAL", "description": "Free Ride", "durationType": "TIME", - "durationValue": 300, + "durationValue": 1800, "durationValueType": None, "targetType": "POWER", - "targetValue": None, - "targetValueLow": 0, - "targetValueHigh": 0.7, - "targetValueType": "PERCENT", + "targetValue": 226, + "targetValueLow": None, + "targetValueHigh": None, + "targetValueType": None, "exerciseCategory": None }]} +payload = {'workoutName': '4x1000m', + 'sport': 'GENERIC', + 'description': 'Uploaded from Rowsandall.com', + 'estimatedDurationInSecs': 2700, + 'estimatedDistanceInMeters': 8768, + 'workoutProvider': 'Rowsandall.com', + 'workoutSourceId': 'Rowsandall.com', + 'steps': [{'type': 'WorkoutStep', + 'stepOrder': 0, + 'repeatType': None, + 'repeatValue': 1, + 'intensity': 'INTERVAL', + 'description': '0', + 'durationType': 'DISTANCE', + 'durationValue': 2000, + 'durationValueType': 'METER', + 'targetType': None, + 'targetValue': None, + 'targetValueLow': None, + 'targetValueHigh': None}, + {'type': 'WorkoutRepeatStep', + 'stepOrder': 1, + 'repeatType': 'REPEAT_UNTIL_STEPS_CMPLT', + 'repeatValue': 4, + 'intensity': 'INTERVAL', + 'description': '3', + 'durationType': 'REPS', + 'durationValue': None, + 'durationValueType': None, + 'targetType': None, + 'targetValue': None, + 'targetValueLow': None, + 'targetValueHigh': None, + 'steps': [{'type': 'WorkoutStep', + 'stepOrder': 2, + 'repeatType': None, + 'repeatValue': 1, + 'intensity': 'INTERVAL', + 'description': '1', + 'durationType': 'DISTANCE', + 'durationValue': 1000, + 'durationValueType': 'METER', + #'targetType': 'CADENCE', + #'targetValue': 25, + #'targetValueLow': None, + #'targetValueHigh': None + }, + {'type': 'WorkoutStep', + 'stepOrder': 3, + 'repeatType': None, + 'repeatValue': 1, + 'intensity': 'REST', + 'description': '2', + 'durationType': 'TIME', + 'durationValue': 60, + 'durationValueType': None, + 'targetType': None, + 'targetValue': None, + 'targetValueLow': None, + 'targetValueHigh': None}]}, + {'type': 'WorkoutStep', + 'stepOrder': 4, + 'repeatType': None, + 'repeatValue': 1, + 'intensity': 'INTERVAL', + 'description': '4', + 'durationType': 'DISTANCE', + 'durationValue': 2000, + 'durationValueType': 'METER', + 'targetType': None, + 'targetValue': None, + 'targetValueLow': None, + 'targetValueHigh': None}]} + + + + print(json.dumps(payload)) @@ -95,4 +173,17 @@ response = requests.post(url,auth=authheaders,json=payload) # build base_string print(response.status_code) -print(response.text) + +print(response.json()) +garmin_workout_id = response.json()['workoutId'] +url = 'http://apis.garmin.com/training-api/schedule' + +payload = { + 'workoutId': garmin_workout_id, + 'date': '2021-05-16' +} + + +response = requests.post(url,auth=authheaders,json=payload) +print(response.status_code) +print(response.json()) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 6d255b64..1636e48e 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -27,13 +27,6 @@ from rowsandall_app.settings import ( from pytz import timezone as tz, utc -#try: -# import http.client as http_client -#except ImportError: - # Python 2 -# import httplib as http_client -#http_client.HTTPConnection.debuglevel = 1 - # You must initialize logging, otherwise you'll not see debug output. #logging.basicConfig() #logging.getLogger().setLevel(logging.DEBUG) @@ -82,6 +75,36 @@ columns = { 'bikeCadenceInRPM':' Cadence (stokes/min)', } +targettypes = { + "Speed": "SPEED", + "HeartRate": "HEART_RATE", + "Open": "OPEN", + "Cadence": "CADENCE", + "Power": "POWER", + "Grade": "GRADE", + "Resistance": "RESISTANCE", + "Power3s": "POWER_3S", + "Power10s": "POWER_10S", + "Power30s": "POWER_30S", + "PowerLap": "POWER_LAP", + "SwimStroke": "SWIM_STROKE", + "SpeedLap": "SPEED_LAP", + "HeartRateLap": "HEART_RATE_LAP" +} + +repeattypes = { + "RepeatUntilStepsCmplt": "REPEAT_UNTIL_STEPS_CMPLT", + "RepeatUntilTime": "REPEAT_UNTIL_TIME", + "RepeatUntilDistance": "REPEAT_UNTIL_TIME", + "RepeatUntilCalories": "REPEAT_UNTIL_CALORIES" , + "RepeatUntilHrLessThan": "REPEAT_UNTIL_HR_LESS_THAN" , + "RepeatUntilHrGreaterThan": "REPEAT_UNTIL_HR_GREATER_THAN", + "RepeatUntilPowerLessThan": "REPEAT_UNTIL_POWER_LESS_THAN", + "RepeatUntilPowerGreaterThan": "REPEAT_UNTIL_POWER_GREATER_THAN", + "RepeatUntilPowerLapLessThan": "REPEAT_UNTIL_POWER_LAP_LESS_THAN", + "RepeatUntilPowerLapGreaterThan": "REPEAT_UNTIL_POWER_LAP_GREATER_THAN", +} + def garmin_authorize(): # pragma: no cover redirect_uri = oauth_data['redirect_uri'] client_secret = oauth_data['client_secret'] @@ -179,9 +202,11 @@ from rowers import utils def step_to_garmin(step,order=0): durationtype = step['dict']['durationType'] durationvalue = step['dict']['durationValue'] - durationvaluetype = '' + durationvaluetype = None try: - intensity = step['dict']['intensity'] + intensity = step['dict']['intensity'].upper() + if intensity.lower() == 'active': + intensity = 'INTERVAL' except KeyError: intensity = None #durationvaluetype = '' @@ -197,56 +222,87 @@ def step_to_garmin(step,order=0): if durationvalue <= 100: durationvaluetype = 'PERCENT' else: - durationvaluetype = '' + durationvaluetype = None durationvalue -= 100 elif durationtype == 'HrGreaterThan': # pragma: no cover durationtype = 'HR_GREATER_THAN' if durationvalue <= 100: durationvaluetype = 'PERCENT' else: - durationvaluetype = '' + durationvaluetype = None durationvalue -= 100 elif durationtype == 'PowerLessThan': # pragma: no cover durationtype = 'POWER_LESS_THAN' if durationvalue <= 1000: durationvaluetype = 'PERCENT' else: - durationvaluetype = '' + durationvaluetype = None durationvalue -= 1000 elif durationtype == 'PowerGreaterThan': # pragma: no cover durationtype = 'POWER_GREATER_THAN' if durationvalue <= 1000: durationvaluetype = 'PERCENT' else: - durationvaluetype = '' + durationvaluetype = None durationvalue -= 1000 elif durationtype == 'Reps': # pragma: no cover durationtype = 'REPS' try: targetType = step['dict']['targetType'] + targetType = targettypes[targetType] except KeyError: targetType = None try: targetValue = step['dict']['targetValue'] + if targetValue == 0: + targetValue = None except KeyError: targetValue = None try: targetValueLow = step['dict']['targetValueLow'] + if targetValueLow == 0: + targetValueLow = None except KeyError: targetValueLow = None try: - targetValueHigh = step['dict']['targetValueHigh'], + targetValueHigh = step['dict']['targetValueHigh'] + if targetValueHigh == 0: + targetValueHigh = None except KeyError: targetValueHigh = None + if targetType.lower() == "power": + targetType = 'POWER' + if targetValue is not None and targetValue > 1000: + targetValue -= 1000 + if targetValueHigh is not None and targetValueHigh > 1000: + targetValueHigh -= 1000 + if targetValueLow is not None and targetValueLow > 1000: + targetValueLow -= 1000 + + if targetValue is None and targetValueLow is None and targetValueHigh is None: + targetType = None + + steptype = 'WorkoutRepeatStep' + if step['type'].lower() == 'step': + steptype = 'WorkoutStep' + + repeattype = None + if steptype == 'WorkoutRepeatStep': + repeattype = repeattypes[step['dict']['durationType']] + durationtype = 'REPS' + durationvalue = None + durationvaluetype = None + targetType = None + targetValue = None out = { - 'type': step['type'], + 'type': steptype, 'stepOrder':order, - 'repeatType':step['type'], + 'repeatType':repeattype, 'repeatValue':step['repeatValue'], 'intensity':intensity, 'description':step['dict']['wkt_step_name'], @@ -303,30 +359,10 @@ 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', - encoding='base64' - ) + return payload - response = garmin.post(url,data=payload) - - #POST /training-api/workout?undefined HTTP/1.1 - #Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0" - #Host: apis.garmin.com - #Accept: */* - - #curl -v --header 'Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0"' 'https://apis.garmin.com/training-api/workout' - - #Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0" - - return response - def get_garmin_permissions(user): # pragma: no cover r = Rower.objects.get(user=user) @@ -334,15 +370,18 @@ def get_garmin_permissions(user): # pragma: no cover s = "Token doesn't exist. Need to authorize" return custom_exception_handler(401,s) - garmin = OAuth1Session(oauth_data['client_id'], - client_secret=oauth_data['client_secret'], - resource_owner_key=r.garmintoken, - resource_owner_secret=r.garminrefreshtoken, - ) + garminheaders = 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', + ) - url = 'https://apis.garmin.com/userPermissions/' - result = garmin.get(url) + url = 'https://apis.garmin.com/userPermissions' + + result = requests.get(url,auth=garminheaders) if result.status_code == 200: return result.json() @@ -351,31 +390,58 @@ def get_garmin_permissions(user): # pragma: no cover def garmin_session_create(ps,user): # pragma: no cover if not ps.steps: + print('aap') return 0 if not garmin_can_export_session(user): + print('noot') return 0 - garmindict = ps_to_garmin(ps) - r = Rower.objects.get(user=user) if (r.garmintoken == '') or (r.garmintoken is None): s = "Token doesn't exist. Need to authorize" return custom_exception_handler(401,s) - garmin = OAuth1Session(oauth_data['client_id'], - client_secret=oauth_data['client_secret'], - resource_owner_key=r.garmintoken, - resource_owner_secret=r.garminrefreshtoken, - ) + payload = ps_to_garmin(ps,r) - url = 'http://apis.garmin.com/training-api/schedule/' + url = 'https://apis.garmin.com/training-api/workout' + + garminheaders = 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 = requests.post(url,auth=garminheaders,json=payload) + print(response.status_code,response.text) - response = garmin.post(url,data=garmindict) if response.status_code != 200: return 0 - return response.json()['workoutId'] + garmin_workout_id = response.json()['workoutId'] + + + url = 'http://apis.garmin.com/training-api/schedule' + + payload = { + 'workoutId': garmin_workout_id, + 'date': ps.preferreddate.strftime('%Y-%m-%d') + } + + + response = requests.post(url,auth=garminheaders,json=payload) + print(response.status_code,response.text) + + if response.status_code != 200: + return 0 + + ps.garmin_schedule_id = response.json() + ps.garmin_workout_id = garmin_workout_id + ps.save() + + return garmin_workout_id def garmin_getworkout(garminid,r,activity): starttime = activity['startTimeInSeconds'] diff --git a/rowers/models.py b/rowers/models.py index efac1c2d..7d612fef 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2399,6 +2399,8 @@ class PlannedSession(models.Model): steps = PlannedSessionStepField(default={},null=True) interval_string = models.TextField(max_length=1000,default=None,blank=True,null=True, verbose_name='Interval String (optional)') + garmin_workout_id = models.BigIntegerField(default=0) + garmin_schedule_id = models.BigIntegerField(default=0) tags = TaggableManager(blank=True) From cbf7c4ab0bc05324bfcdd2317992144d16e133d8 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 16 May 2021 15:33:03 +0200 Subject: [PATCH 2/9] adding simple export to Garmin --- garminscript.py | 4 +-- rowers/garmin_stuff.py | 25 +++++++++++----- rowers/models.py | 14 +++++++++ rowers/plannedsessions.py | 2 +- rowers/templates/plannedsessionview.html | 5 ++++ rowers/templates/rower_exportsettings.html | 5 +++- rowers/urls.py | 2 ++ rowers/utils.py | 17 ++++++++--- rowers/views/planviews.py | 33 ++++++++++++++++++++++ 9 files changed, 92 insertions(+), 15 deletions(-) diff --git a/garminscript.py b/garminscript.py index 9cb803af..f29698ec 100644 --- a/garminscript.py +++ b/garminscript.py @@ -66,8 +66,8 @@ payload2 = { "exerciseCategory": None }]} -payload = {'workoutName': '4x1000m', - 'sport': 'GENERIC', +payload = {'workoutName': '4x1000m row', + 'sport': 'CARDIO_TRAINING', 'description': 'Uploaded from Rowsandall.com', 'estimatedDurationInSecs': 2700, 'estimatedDistanceInMeters': 8768, diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 1636e48e..9b5bf773 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -11,6 +11,8 @@ from rowers.mytypes import otwtypes from rowers.rower_rules import is_workout_user,ispromember from iso8601 import ParseError +from rowers.plannedsessions import ps_dict_get_description + import pandas as pd @@ -131,6 +133,7 @@ def garmin_processcallback(redirect_response,resource_owner_key,resource_owner_s verifier = oauth_response.get('oauth_verifier') token = oauth_response.get('oauth_token') access_token_url = 'https://connectapi.garmin.com/oauth-service/oauth/access_token' + print(token,access_token_url) # Using OAuth1Session garmin = OAuth1Session(oauth_data['client_id'], @@ -191,6 +194,8 @@ def get_garmin_workout_list(user): # pragma: no cover return result def garmin_can_export_session(user): # pragma: no cover + if user.rower.rowerplan not in ['coach','plan']: + return False result = get_garmin_permissions(user) if 'WORKOUT_IMPORT' in result: return True @@ -254,7 +259,6 @@ def step_to_garmin(step,order=0): except KeyError: targetType = None - try: targetValue = step['dict']['targetValue'] if targetValue == 0: @@ -283,6 +287,13 @@ def step_to_garmin(step,order=0): if targetValueLow is not None and targetValueLow > 1000: targetValueLow -= 1000 + # temp - throw away target other than power + if targetType.lower() != 'power': + targetType = None + targetValue = None + targetValueLow = None + targetValueHigh = None + if targetValue is None and targetValueLow is None and targetValueHigh is None: targetType = None @@ -333,8 +344,8 @@ def step_to_garmin(step,order=0): def ps_to_garmin(ps,r): payload = { 'workoutName': ps.name, - 'sport': 'GENERIC', - 'description':'Uploaded from Rowsandall.com', + 'sport':r.garminactivity, + 'description':ps_dict_get_description(ps.steps), 'estimatedDurationInSecs':60*ps.approximate_duration, 'estimatedDistanceInMeters': ps.approximate_distance, 'workoutProvider': 'Rowsandall.com', @@ -390,12 +401,13 @@ def get_garmin_permissions(user): # pragma: no cover def garmin_session_create(ps,user): # pragma: no cover if not ps.steps: - print('aap') return 0 if not garmin_can_export_session(user): - print('noot') return 0 + if ps.garmin_schedule_id != 0: + return ps.garmin_schedule_id + r = Rower.objects.get(user=user) if (r.garmintoken == '') or (r.garmintoken is None): s = "Token doesn't exist. Need to authorize" @@ -414,7 +426,7 @@ def garmin_session_create(ps,user): # pragma: no cover ) response = requests.post(url,auth=garminheaders,json=payload) - print(response.status_code,response.text) + if response.status_code != 200: @@ -432,7 +444,6 @@ def garmin_session_create(ps,user): # pragma: no cover response = requests.post(url,auth=garminheaders,json=payload) - print(response.status_code,response.text) if response.status_code != 200: return 0 diff --git a/rowers/models.py b/rowers/models.py index 7d612fef..d5449391 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -837,6 +837,15 @@ class Rower(models.Model): ('None','None'), ) + garminsports = ( + ('GENERIC','Custom'), + ('RUNNING','Running'), + ('CYCLING','Cycling'), + ('LAP_SWIMMING','Lap Swimming'), + ('STRENGTH_TRAINING','Strength Training'), + ('CARDIO_TRAINING','Cardio Training'), + ) + user = models.OneToOneField(User,on_delete=models.CASCADE) #billing details @@ -1015,6 +1024,10 @@ class Rower(models.Model): garminrefreshtoken = models.CharField(default='',max_length=1000, blank=True,null=True) + garminactivity = models.CharField(default='RUNNING',max_length=200, + verbose_name='Garmin Activity for Structured Workouts', + choices=garminsports) + stravatoken = models.CharField(default='',max_length=200,blank=True,null=True) stravatokenexpirydate = models.DateTimeField(blank=True,null=True) stravarefreshtoken = models.CharField(default='',max_length=1000, @@ -3853,6 +3866,7 @@ class RowerExportForm(ModelForm): model = Rower fields = [ 'stravaexportas', + 'garminactivity', 'polar_auto_import', 'c2_auto_export', 'c2_auto_import', diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 687ac4a4..60e82e56 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -86,7 +86,7 @@ from rowers.tasks import ( from rowers.utils import totaltime_sec_to_string def ps_dict_get_description(d,short=False): # pragma: no cover - sdict,totalmeters,totalseconds,totalrscore = ps_dict_order(d,short=short) + sdict,totalmeters,totalseconds,totalrscore = ps_dict_order(d,short=short,html=False) s = '' for item in sdict: s += item['string']+'\n' diff --git a/rowers/templates/plannedsessionview.html b/rowers/templates/plannedsessionview.html index 21d57028..2389ffcc 100644 --- a/rowers/templates/plannedsessionview.html +++ b/rowers/templates/plannedsessionview.html @@ -18,6 +18,11 @@ Edit Session / Save to Library + {% if psdict.garmin_schedule_id.1 %} + Exported to Garmin + {% else %} + Export to Garmin + {% endif %}

{% endif %}

Session {{ psdict.name.1 }}

diff --git a/rowers/templates/rower_exportsettings.html b/rowers/templates/rower_exportsettings.html index 79332c87..e631af88 100644 --- a/rowers/templates/rower_exportsettings.html +++ b/rowers/templates/rower_exportsettings.html @@ -63,7 +63,10 @@ {% endif %}

Garmin Connnect has no manual sync, so connecting your account to your Garmin account will - automatically auto-sync workouts from Garmin to Rowsandall (but not in the other direction). + automatically auto-sync workouts from Garmin to Rowsandall (but not in the other direction). If you + want to export our structured workout sessions to your Garmin device, you have to set the "Garmin Activity" + to a activity type that is supported by your watch. Not all watches support "Custom" activities, so + you may have to set your activity to Run or Ride while rowing.

Strava Auto Import also imports activity changes on Strava to Rowsandall, except when you delete diff --git a/rowers/urls.py b/rowers/urls.py index 0377375f..7f002f21 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -769,6 +769,8 @@ urlpatterns = [ name='plannedsession_templateedit_view'), re_path(r'^sessions/(?P\d+)/maketemplate/$',views.plannedsession_totemplate_view, name='plannedsession_totemplate_view'), + re_path(r'^sessions/(?P\d+)/togarmin/$',views.plannedsession_togarmin_view, + name='plannedsession_togarmin_view'), re_path(r'^sessions/(?P\d+)/compare/$', views.plannedsession_compare_view, name='plannedsession_compare_view'), diff --git a/rowers/utils.py b/rowers/utils.py index defdd0af..0c1a35d4 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -775,7 +775,7 @@ def ps_dict_order_dict(d,short=False): return sdict2 -def ps_dict_order(d,short=False,rower=None): +def ps_dict_order(d,short=False,rower=None,html=True): sdict = collections.OrderedDict({}) steps = d['steps'] @@ -836,7 +836,10 @@ def ps_dict_order(d,short=False,rower=None): holduntil.append(item['repeatID']) multiplier.append(item['repeatValue']) factor *= item['repeatValue'] - spaces += '   ' + if html: + spaces += '   ' + else: + spaces += ' ' if item['type'] == 'Step': item['string'] = spaces+item['string'] sdict3.append(item) @@ -847,7 +850,10 @@ def ps_dict_order(d,short=False,rower=None): if item['stepID'] == holduntil[-1]: sdict3.append(hold.pop()) factor /= multiplier.pop() - spaces = spaces[:-18] + if html: + spaces = spaces[:-18] + else: + spaces = spaces[:-3] holduntil.pop() else: # pragma: no cover prevstep = sdict3.pop() @@ -861,7 +867,10 @@ def ps_dict_order(d,short=False,rower=None): factor /= multiplier.pop() sdict3.append(prevstep) holduntil.pop() - spaces = spaces[:-18] + if html: + spaces = spaces[:-18] + else: + spaces = spaces[:-3] sdict = list(reversed(sdict3)) diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index dc117e6b..e9a75884 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -8,6 +8,7 @@ from rowingdata import trainingparser import json from taggit.models import Tag +import rowers.garmin_stuff as gs @login_required @permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True) @@ -1979,6 +1980,37 @@ def plannedsession_templateedit_view(request,id=0): }) +@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True) +@user_passes_test(can_plan, login_url="/rowers/paidplans/", + message="This functionality requires a Coach or Self-Coach plan", + redirect_field_name=None) +def plannedsession_togarmin_view(request,id=0): + + r = getrequestplanrower(request) + + startdate, enddate = get_dates_timeperiod(request) + + ps = get_object_or_404(PlannedSession,pk=id) + + result = gs.garmin_session_create(ps,r.user) + + if not result: + messages.error(request,'You failed to export your session to Garmin Connect') + else: + messages.info(request,'Session is now on Garmin Connect. Sync your Garmin watch') + + url = reverse(plannedsession_view,kwargs={'userid':r.user.id, + 'id':ps.id,}) + + startdatestring = startdate.strftime('%Y-%m-%d') + enddatestring = enddate.strftime('%Y-%m-%d') + url += '?when='+startdatestring+'/'+enddatestring + + next = request.GET.get('next', url) + + return HttpResponseRedirect(next) + + @permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True) @user_passes_test(can_plan, login_url="/rowers/paidplans/", message="This functionality requires a Coach or Self-Coach plan", @@ -2385,6 +2417,7 @@ def plannedsession_view(request,id=0,userid=0): steps = ps_dict_get_description_html(d,short=False) + return render(request,'plannedsessionview.html', { 'psdict': psdict, From a9da920ffa389050608130f772bf4467544251a1 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 16 May 2021 16:07:24 +0200 Subject: [PATCH 3/9] fixing tests --- rowers/garmin_stuff.py | 4 ++-- rowers/tests/test_plans.py | 4 +++- rowers/tests/test_user.py | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 9b5bf773..2943a26a 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -278,7 +278,7 @@ def step_to_garmin(step,order=0): except KeyError: targetValueHigh = None - if targetType.lower() == "power": + if targetType is not None and targetType.lower() == "power": targetType = 'POWER' if targetValue is not None and targetValue > 1000: targetValue -= 1000 @@ -288,7 +288,7 @@ def step_to_garmin(step,order=0): targetValueLow -= 1000 # temp - throw away target other than power - if targetType.lower() != 'power': + if targetType is not None and targetType.lower() != 'power': targetType = None targetValue = None targetValueLow = None diff --git a/rowers/tests/test_plans.py b/rowers/tests/test_plans.py index ecb90802..c9085517 100644 --- a/rowers/tests/test_plans.py +++ b/rowers/tests/test_plans.py @@ -13,6 +13,8 @@ from rowers import garmin_stuff import rowers.plannedsessions as plannedsessions from django.db import transaction +import json + from rowers.views.workoutviews import plannedsession_compare_view from rowers.views.otherviews import download_fit from rowers.opaque import encoder @@ -1899,7 +1901,7 @@ description: "" self.assertEqual(len(stepsdict),2) response = garmin_stuff.ps_to_garmin(self.ps_trimp,self.r) - self.assertEqual(response.status_code,200) + self.assertEqual(len(json.dumps(response)),861) url = '0' request = self.factory.get(url) diff --git a/rowers/tests/test_user.py b/rowers/tests/test_user.py index 47ed565b..cf604395 100644 --- a/rowers/tests/test_user.py +++ b/rowers/tests/test_user.py @@ -143,6 +143,7 @@ class UserPreferencesTest(TestCase): form_data = { 'stravaexportas':'Rowing', + 'garminactivity': 'RUNNING', 'polar_auto_import':True, 'c2_auto_export':False, 'c2_auto_import':False, From e88553a04c931726418bdc0c1b4c89f81e20818d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 16 May 2021 18:03:45 +0200 Subject: [PATCH 4/9] adding test plans --- rowers/tests/test_plans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/tests/test_plans.py b/rowers/tests/test_plans.py index c9085517..c30433af 100644 --- a/rowers/tests/test_plans.py +++ b/rowers/tests/test_plans.py @@ -1901,7 +1901,7 @@ description: "" self.assertEqual(len(stepsdict),2) response = garmin_stuff.ps_to_garmin(self.ps_trimp,self.r) - self.assertEqual(len(json.dumps(response)),861) + self.assertTrue(len(json.dumps(response))>800) url = '0' request = self.factory.get(url) From d195f03a55af5af538f6ed065067254db25e7e2b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 17 May 2021 09:07:04 +0200 Subject: [PATCH 5/9] adding some tests for garmin training api --- rowers/garmin_stuff.py | 19 ++++++++-------- rowers/tests/mocks.py | 30 +++++++++++++++++++++++++ rowers/tests/test_imports.py | 43 ++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 10 deletions(-) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 2943a26a..3fbe874f 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -133,7 +133,7 @@ def garmin_processcallback(redirect_response,resource_owner_key,resource_owner_s verifier = oauth_response.get('oauth_verifier') token = oauth_response.get('oauth_token') access_token_url = 'https://connectapi.garmin.com/oauth-service/oauth/access_token' - print(token,access_token_url) + # Using OAuth1Session garmin = OAuth1Session(oauth_data['client_id'], @@ -193,9 +193,9 @@ def get_garmin_workout_list(user): # pragma: no cover return result -def garmin_can_export_session(user): # pragma: no cover +def garmin_can_export_session(user): if user.rower.rowerplan not in ['coach','plan']: - return False + return False # pragma: no cover result = get_garmin_permissions(user) if 'WORKOUT_IMPORT' in result: return True @@ -215,10 +215,10 @@ def step_to_garmin(step,order=0): except KeyError: intensity = None #durationvaluetype = '' - if durationtype == 'Time': # pragma: no cover + if durationtype == 'Time': durationtype = 'TIME' durationvalue = int(durationvalue/1000.) - elif durationtype == 'Distance': # pragma: no cover + elif durationtype == 'Distance': durationtype = 'DISTANCE' durationvalue = int(durationvalue/100) durationvaluetype = 'METER' @@ -375,7 +375,7 @@ def ps_to_garmin(ps,r): -def get_garmin_permissions(user): # pragma: no cover +def get_garmin_permissions(user): r = Rower.objects.get(user=user) if (r.garmintoken == '') or (r.garmintoken is None): s = "Token doesn't exist. Need to authorize" @@ -399,7 +399,7 @@ def get_garmin_permissions(user): # pragma: no cover return [] -def garmin_session_create(ps,user): # pragma: no cover +def garmin_session_create(ps,user): if not ps.steps: return 0 if not garmin_can_export_session(user): @@ -427,15 +427,13 @@ def garmin_session_create(ps,user): # pragma: no cover response = requests.post(url,auth=garminheaders,json=payload) - - if response.status_code != 200: return 0 garmin_workout_id = response.json()['workoutId'] - url = 'http://apis.garmin.com/training-api/schedule' + url = 'https://apis.garmin.com/training-api/schedule' payload = { 'workoutId': garmin_workout_id, @@ -444,6 +442,7 @@ def garmin_session_create(ps,user): # pragma: no cover response = requests.post(url,auth=garminheaders,json=payload) + if response.status_code != 200: return 0 diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 66ba56bf..80ad25d5 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -874,6 +874,10 @@ def mocked_requests(*args, **kwargs): return MockResponse(json_data,200) + if len(args)==1 and 'userPermissions' in args[0]: + json_data = ['WORKOUT_IMPORT','ACTIVITY_EXPORT'] + return MockResponse(json_data,200) + if 'garmin' in args: return MockOAuth1Session() @@ -882,6 +886,8 @@ def mocked_requests(*args, **kwargs): args = [kwargs['url']] if "tofit" in kwargs['url']: args = [kwargs['url']] + if "tojson" in kwargs['url']: + args = [kwargs['url']] if not args: return MockSession() @@ -997,10 +1003,23 @@ def mocked_requests(*args, **kwargs): garmindownloadregex = '.*?garmin\.com\/mockfile?id=1' garmindownloadtester = re.compile(garmindownloadregex) + garmintrainingregex = '.*?garmin\.com\/training-api\/workout' + garmintrainingtester = re.compile(garmintrainingregex) + + garmintrainingscheduleregex = '.*?garmin\.com\/training-api\/schedule' + garmintrainingscheduletester = re.compile(garmintrainingscheduleregex) if garmintester.match(args[0]): if garmindownloadtester.match(args[0]): return MockStreamResponse('rowers/tests/testdata/3x250m.fit',200) + if garmintrainingtester.match(args[0]): + json_data = { + 'workoutId':1212, + } + return MockResponse(json_data,200) + if garmintrainingscheduletester.match(args[0]): + json_data = 1234 + return MockResponse(json_data,200) if stravaathletetester.match(args[0]): json_data = stravaathletejson @@ -1254,6 +1273,8 @@ class MockResponse: def json(self): return self.json_data + + class MockOAuth1Session: def __init__(self,*args, **kwargs): pass @@ -1264,5 +1285,14 @@ class MockOAuth1Session: def post(*args, **kwargs): return MockResponse({},200) + def fetch_request_token(*args, **kwargs): + return { + 'oauth_token':'aap', + 'oauth_token_secret':'noot', + } + + def authorization_url(*args, **kwargs): + return 'url' + def mocked_invoiceid(*args,**kwargs): return 1 diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index bfef0eee..556f3b93 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -18,6 +18,9 @@ from rowers import stravastuff import urllib import json +from django.db import transaction +import rowers.garmin_stuff as gs + @pytest.mark.django_db @override_settings(TESTING=True) class GarminObjects(DjangoTestCase): @@ -35,11 +38,31 @@ class GarminObjects(DjangoTestCase): ) self.r.garmintoken = 'dfdzf' self.r.garminrefreshtoken = 'fsls' + self.r.rowerplan = 'plan' self.r.save() self.c.login(username='john',password='koeinsloot') self.nu = datetime.datetime.now() + startdate = nu.date() + enddate = (nu+datetime.timedelta(days=3)).date() + preferreddate = startdate + + self.ps_trimp = SessionFactory( + startdate=startdate,enddate=enddate, + sessiontype='test', + sessionmode = 'TRIMP', + criterium = 'none', + sessionvalue = 77, + sessionunit='none', + preferreddate=preferreddate, + manager=self.u, + ) + + self.ps_trimp.interval_string = '10min+4x1000m@200W/20sec+2000m@24spm+10min' + self.ps_trimp.save() + + def tearDown(self): ws = Workout.objects.filter(user=self.r) for w in ws: @@ -137,6 +160,26 @@ class GarminObjects(DjangoTestCase): self.assertEqual(res,1) + @patch('rowers.garmin_stuff.OAuth1Session') + def notest_garmin_callback(self,MockOAuth1Session): + with transaction.atomic(): + response = self.c.get('/garmin_callback/?oauth_token=528ea5d9-1163-434d-b172-f428c5d9f522&oauth_verifier=LW33ZMBP8H') + self.assertEqual(response.status_code, 200) + + @patch('rowers.garmin_stuff.requests.get',side_effect=mocked_requests) + def test_garmin_can_export_session(self,mock_get): + result = gs.garmin_can_export_session(self.u) + self.assertTrue(result) + + def test_ps_to_garmin(self): + res = gs.ps_to_garmin(self.ps_trimp,self.r) + self.assertTrue(len(json.dumps(res))>500) + + @patch('rowers.garmin_stuff.requests.get',side_effect=mocked_requests) + @patch('rowers.garmin_stuff.requests.post',side_effect=mocked_requests) + def test_garmin_session_create(self,mock_get,mock_post): + res = gs.garmin_session_create(self.ps_trimp,self.u) + self.assertEqual(res,1212) @pytest.mark.django_db From ce9621890ab7da4ed45a9f08e21ff2e9ed292582 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 17 May 2021 09:53:00 +0200 Subject: [PATCH 6/9] more tests and coverage --- rowers/garmin_stuff.py | 30 +++++++++++++++--------------- rowers/stravastuff.py | 2 +- rowers/tasks.py | 2 +- rowers/tests/test_imports.py | 8 ++++++++ rowers/views/planviews.py | 6 +++--- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 3fbe874f..ee4d1e9f 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -200,7 +200,7 @@ def garmin_can_export_session(user): if 'WORKOUT_IMPORT' in result: return True - return False + return False # pragma: no cover from rowers import utils @@ -261,19 +261,19 @@ def step_to_garmin(step,order=0): try: targetValue = step['dict']['targetValue'] - if targetValue == 0: + if targetValue == 0: # pragma: no cover targetValue = None except KeyError: targetValue = None try: targetValueLow = step['dict']['targetValueLow'] - if targetValueLow == 0: + if targetValueLow == 0: # pragma: no cover targetValueLow = None except KeyError: targetValueLow = None try: targetValueHigh = step['dict']['targetValueHigh'] - if targetValueHigh == 0: + if targetValueHigh == 0: # pragma: no cover targetValueHigh = None except KeyError: targetValueHigh = None @@ -282,9 +282,9 @@ def step_to_garmin(step,order=0): targetType = 'POWER' if targetValue is not None and targetValue > 1000: targetValue -= 1000 - if targetValueHigh is not None and targetValueHigh > 1000: + if targetValueHigh is not None and targetValueHigh > 1000: # pragma: no cover targetValueHigh -= 1000 - if targetValueLow is not None and targetValueLow > 1000: + if targetValueLow is not None and targetValueLow > 1000: # pragma: no cover targetValueLow -= 1000 # temp - throw away target other than power @@ -377,7 +377,7 @@ def ps_to_garmin(ps,r): def get_garmin_permissions(user): r = Rower.objects.get(user=user) - if (r.garmintoken == '') or (r.garmintoken is None): + if (r.garmintoken == '') or (r.garmintoken is None): # pragma: no cover s = "Token doesn't exist. Need to authorize" return custom_exception_handler(401,s) @@ -397,19 +397,19 @@ def get_garmin_permissions(user): if result.status_code == 200: return result.json() - return [] + return [] # pragma: no cover def garmin_session_create(ps,user): if not ps.steps: - return 0 + return 0 # pragma: no cover if not garmin_can_export_session(user): - return 0 + return 0 # pragma: no cover if ps.garmin_schedule_id != 0: - return ps.garmin_schedule_id + return ps.garmin_schedule_id # pragma: no cover r = Rower.objects.get(user=user) - if (r.garmintoken == '') or (r.garmintoken is None): + if (r.garmintoken == '') or (r.garmintoken is None): # pragma: no cover s = "Token doesn't exist. Need to authorize" return custom_exception_handler(401,s) @@ -427,7 +427,7 @@ def garmin_session_create(ps,user): response = requests.post(url,auth=garminheaders,json=payload) - if response.status_code != 200: + if response.status_code != 200: # pragma: no cover return 0 garmin_workout_id = response.json()['workoutId'] @@ -442,9 +442,9 @@ def garmin_session_create(ps,user): response = requests.post(url,auth=garminheaders,json=payload) - - if response.status_code != 200: + + if response.status_code != 200: # pragma: no cover return 0 ps.garmin_schedule_id = response.json() diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py index 5ff2fda8..7e13afbd 100644 --- a/rowers/stravastuff.py +++ b/rowers/stravastuff.py @@ -383,7 +383,7 @@ def async_get_workout(user,stravaid): # Get a Strava workout summary data and stroke data by ID def get_workout(user,stravaid,do_async=False): - if do_async: + if do_async: # pragma: no cover res = async_get_workout(user,stravaid) return {},pd.DataFrame() try: diff --git a/rowers/tasks.py b/rowers/tasks.py index b4d9b3b2..32c050d6 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -3364,7 +3364,7 @@ def fetch_strava_workout(stravatoken,oauth_data,stravaid,csvfilename,userid,debu try: thetimezone = workoutsummary['timezone'] - except: + except: # pragma: no cover thetimezone = 'UTC' try: diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index 556f3b93..05706e37 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -181,6 +181,14 @@ class GarminObjects(DjangoTestCase): res = gs.garmin_session_create(self.ps_trimp,self.u) self.assertEqual(res,1212) + @patch('rowers.garmin_stuff.requests.get',side_effect=mocked_requests) + @patch('rowers.garmin_stuff.requests.post',side_effect=mocked_requests) + def test_togarmin_view(self,mock_get,mock_post): + url = reverse('plannedsession_togarmin_view',kwargs={'id':self.ps_trimp.id}) + response = self.c.get(url,follow=True) + + self.assertEqual(response.status_code,200) + @pytest.mark.django_db @override_settings(TESTING=True) diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index e9a75884..9d02627a 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -1994,9 +1994,9 @@ def plannedsession_togarmin_view(request,id=0): result = gs.garmin_session_create(ps,r.user) - if not result: - messages.error(request,'You failed to export your session to Garmin Connect') - else: + if not result: # pragma: no cover + messages.error(request,'You failed to export your session to Garmin Connect') # pragma: no cover + else: # pragma: no cover messages.info(request,'Session is now on Garmin Connect. Sync your Garmin watch') url = reverse(plannedsession_view,kwargs={'userid':r.user.id, From f803878691e58c83005a37a07e9e6b8f85bf0467 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 17 May 2021 17:54:56 +0200 Subject: [PATCH 7/9] using targetValueHigh and targetValueLow as recommended --- rowers/garmin_stuff.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index ee4d1e9f..5237f927 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -267,32 +267,38 @@ def step_to_garmin(step,order=0): targetValue = None try: targetValueLow = step['dict']['targetValueLow'] - if targetValueLow == 0: # pragma: no cover + if targetValueLow == 0 and targetValue is not None and targetValue > 0: + targetValueLow = targetValue + targetValue = None + elif targetValueLow == 0: # pragma: no cover targetValueLow = None except KeyError: targetValueLow = None try: targetValueHigh = step['dict']['targetValueHigh'] - if targetValueHigh == 0: # pragma: no cover + if targetValue is not None and targetValue > 0 and targetValueHigh == 0: + targetValueHigh = targetValue + targetValue = 0 + elif targetValueHigh == 0: # pragma: no cover targetValueHigh = None except KeyError: targetValueHigh = None if targetType is not None and targetType.lower() == "power": targetType = 'POWER' - if targetValue is not None and targetValue > 1000: - targetValue -= 1000 - if targetValueHigh is not None and targetValueHigh > 1000: # pragma: no cover - targetValueHigh -= 1000 - if targetValueLow is not None and targetValueLow > 1000: # pragma: no cover - targetValueLow -= 1000 + #if targetValue is not None and targetValue > 1000: + # targetValue -= 1000 + #if targetValueHigh is not None and targetValueHigh > 1000: # pragma: no cover + # targetValueHigh -= 1000 + #if targetValueLow is not None and targetValueLow > 1000: # pragma: no cover + # targetValueLow -= 1000 # temp - throw away target other than power - if targetType is not None and targetType.lower() != 'power': - targetType = None - targetValue = None - targetValueLow = None - targetValueHigh = None + #if targetType is not None and targetType.lower() != 'power': + # targetType = None + # targetValue = None + # targetValueLow = None + # targetValueHigh = None if targetValue is None and targetValueLow is None and targetValueHigh is None: targetType = None From 44c13ebe2496ce5ffc390b591fd69d626df614d6 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 18 May 2021 07:53:06 +0200 Subject: [PATCH 8/9] ugly code to convert to Garmin API payload --- rowers/garmin_stuff.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 5237f927..43335cc8 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -265,6 +265,16 @@ def step_to_garmin(step,order=0): targetValue = None except KeyError: targetValue = None + + if targetType is not None and targetType.lower() == "power": + targetType = 'POWER' + if targetValue is not None and targetValue <= 1000: + targetValueType = 'PERCENT' + else: + targetValueType = None + targetValue -= 1000 + + try: targetValueLow = step['dict']['targetValueLow'] if targetValueLow == 0 and targetValue is not None and targetValue > 0: @@ -272,6 +282,10 @@ def step_to_garmin(step,order=0): targetValue = None elif targetValueLow == 0: # pragma: no cover targetValueLow = None + elif targetValueLow <= 1000 and targetType == 'POWER': + targetValueType = 'PERCENT' + elif targetValueLow > 1000 and targetType == 'POWER': + targetValueLow -= 1000 except KeyError: targetValueLow = None try: @@ -279,26 +293,15 @@ def step_to_garmin(step,order=0): if targetValue is not None and targetValue > 0 and targetValueHigh == 0: targetValueHigh = targetValue targetValue = 0 + elif targetValueHigh <= 1000 and targetType == 'POWER': + targetValueType = 'PERCENT' + elif targetValueHigh > 1000 and targetType == 'POWER': + targetValueHigh -= 1000 elif targetValueHigh == 0: # pragma: no cover targetValueHigh = None except KeyError: targetValueHigh = None - if targetType is not None and targetType.lower() == "power": - targetType = 'POWER' - #if targetValue is not None and targetValue > 1000: - # targetValue -= 1000 - #if targetValueHigh is not None and targetValueHigh > 1000: # pragma: no cover - # targetValueHigh -= 1000 - #if targetValueLow is not None and targetValueLow > 1000: # pragma: no cover - # targetValueLow -= 1000 - - # temp - throw away target other than power - #if targetType is not None and targetType.lower() != 'power': - # targetType = None - # targetValue = None - # targetValueLow = None - # targetValueHigh = None if targetValue is None and targetValueLow is None and targetValueHigh is None: targetType = None From 867825b607037e603f7446b7f8fb90adaf03a371 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 18 May 2021 08:34:06 +0200 Subject: [PATCH 9/9] coverage and test --- rowers/garmin_stuff.py | 14 +++++++------- rowers/tasks.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rowers/garmin_stuff.py b/rowers/garmin_stuff.py index 43335cc8..f82334a7 100644 --- a/rowers/garmin_stuff.py +++ b/rowers/garmin_stuff.py @@ -269,7 +269,7 @@ def step_to_garmin(step,order=0): if targetType is not None and targetType.lower() == "power": targetType = 'POWER' if targetValue is not None and targetValue <= 1000: - targetValueType = 'PERCENT' + targetValueType = 'PERCENT' # pragma: no cover else: targetValueType = None targetValue -= 1000 @@ -277,25 +277,25 @@ def step_to_garmin(step,order=0): try: targetValueLow = step['dict']['targetValueLow'] - if targetValueLow == 0 and targetValue is not None and targetValue > 0: + if targetValueLow == 0 and targetValue is not None and targetValue > 0: # pragma: no cover targetValueLow = targetValue targetValue = None elif targetValueLow == 0: # pragma: no cover targetValueLow = None - elif targetValueLow <= 1000 and targetType == 'POWER': + elif targetValueLow <= 1000 and targetType == 'POWER': # pragma: no cover targetValueType = 'PERCENT' - elif targetValueLow > 1000 and targetType == 'POWER': + elif targetValueLow > 1000 and targetType == 'POWER': # pragma: no cover targetValueLow -= 1000 except KeyError: targetValueLow = None try: targetValueHigh = step['dict']['targetValueHigh'] - if targetValue is not None and targetValue > 0 and targetValueHigh == 0: + if targetValue is not None and targetValue > 0 and targetValueHigh == 0: # pragma: no cover targetValueHigh = targetValue targetValue = 0 - elif targetValueHigh <= 1000 and targetType == 'POWER': + elif targetValueHigh <= 1000 and targetType == 'POWER': # pragma: no cover targetValueType = 'PERCENT' - elif targetValueHigh > 1000 and targetType == 'POWER': + elif targetValueHigh > 1000 and targetType == 'POWER': # pragma: no cover targetValueHigh -= 1000 elif targetValueHigh == 0: # pragma: no cover targetValueHigh = None diff --git a/rowers/tasks.py b/rowers/tasks.py index ea06d589..5ce2807d 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -2993,7 +2993,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone verified = data['verified'] try: startdatetime = iso8601.parse_date(data['date_utc']) - except: + except: # pragma: no cover startdatetime = iso8601.parse_date(data['date']) weightclass = data['weight_class']