diff --git a/garminscript.py b/garminscript.py index b288cd1e..f29698ec 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 row', + 'sport': 'CARDIO_TRAINING', + '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..f82334a7 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 @@ -27,13 +29,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 +77,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'] @@ -109,6 +134,7 @@ def garmin_processcallback(redirect_response,resource_owner_key,resource_owner_s token = oauth_response.get('oauth_token') access_token_url = 'https://connectapi.garmin.com/oauth-service/oauth/access_token' + # Using OAuth1Session garmin = OAuth1Session(oauth_data['client_id'], client_secret=oauth_data['client_secret'], @@ -167,28 +193,32 @@ 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 # pragma: no cover result = get_garmin_permissions(user) if 'WORKOUT_IMPORT' in result: return True - return False + return False # pragma: no cover 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 = '' - 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' @@ -197,56 +227,102 @@ 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: # pragma: no cover + 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' # pragma: no cover + else: + targetValueType = None + targetValue -= 1000 + + try: targetValueLow = step['dict']['targetValueLow'] + 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': # pragma: no cover + targetValueType = 'PERCENT' + elif targetValueLow > 1000 and targetType == 'POWER': # pragma: no cover + targetValueLow -= 1000 except KeyError: targetValueLow = None try: - targetValueHigh = step['dict']['targetValueHigh'], + targetValueHigh = step['dict']['targetValueHigh'] + if targetValue is not None and targetValue > 0 and targetValueHigh == 0: # pragma: no cover + targetValueHigh = targetValue + targetValue = 0 + elif targetValueHigh <= 1000 and targetType == 'POWER': # pragma: no cover + targetValueType = 'PERCENT' + elif targetValueHigh > 1000 and targetType == 'POWER': # pragma: no cover + targetValueHigh -= 1000 + elif targetValueHigh == 0: # pragma: no cover + targetValueHigh = None except KeyError: targetValueHigh = None + 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'], @@ -277,8 +353,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', @@ -303,79 +379,88 @@ 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 +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) - 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() - return [] + return [] # pragma: no cover -def garmin_session_create(ps,user): # 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 - garmindict = ps_to_garmin(ps) + if ps.garmin_schedule_id != 0: + 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) - 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' - response = garmin.post(url,data=garmindict) + 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', + ) - if response.status_code != 200: + response = requests.post(url,auth=garminheaders,json=payload) + + if response.status_code != 200: # pragma: no cover return 0 - return response.json()['workoutId'] + garmin_workout_id = response.json()['workoutId'] + + + url = 'https://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) + + + if response.status_code != 200: # pragma: no cover + 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..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, @@ -2399,6 +2412,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) @@ -3851,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/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 2972937e..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'] @@ -3367,7 +3367,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/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 %}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/tests/mocks.py b/rowers/tests/mocks.py
index eddf1473..7dea2622 100644
--- a/rowers/tests/mocks.py
+++ b/rowers/tests/mocks.py
@@ -876,6 +876,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()
@@ -884,6 +888,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()
@@ -999,10 +1005,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
@@ -1258,6 +1277,8 @@ class MockResponse:
def json(self):
return self.json_data
+
+
class MockOAuth1Session:
def __init__(self,*args, **kwargs):
pass
@@ -1268,5 +1289,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 7bd662ef..dfc65665 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,7 +160,35 @@ 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)
+
+ @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/tests/test_plans.py b/rowers/tests/test_plans.py
index ecb90802..c30433af 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.assertTrue(len(json.dumps(response))>800)
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,
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