Private
Public Access
1
0

Merge branch 'feature/garminstuff' into develop

This commit is contained in:
Sander Roosendaal
2021-05-18 08:34:29 +02:00
15 changed files with 418 additions and 90 deletions

View File

@@ -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())

View File

@@ -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']

View File

@@ -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',

View File

@@ -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'

View File

@@ -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:

View File

@@ -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:

View File

@@ -18,6 +18,11 @@
<i class="fas fa-pencil-alt fa-fw"></i> Edit Session</a>
/
<a href="/rowers/sessions/{{ psdict.id.1 }}/maketemplate/?next={{ request.path|urlencode }}"><i class="fas fa-books fa-fw"></i> Save to Library</a>
{% if psdict.garmin_schedule_id.1 %}
<i class="fas fa-watch-fitness fa-fw"></i>Exported to Garmin
{% else %}
<a href="/rowers/sessions/{{ psdict.id.1 }}/togarmin/?next={{ request.path|urlencode }}"><i class="fas fa-watch-fitness fa-fw"></i> Export to Garmin</a>
{% endif %}
</p>
{% endif %}
<h1>Session {{ psdict.name.1 }}</h1>

View File

@@ -63,7 +63,10 @@
{% endif %}
<p>
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.
</p>
<p>
Strava Auto Import also imports activity changes on Strava to Rowsandall, except when you delete

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,

View File

@@ -769,6 +769,8 @@ urlpatterns = [
name='plannedsession_templateedit_view'),
re_path(r'^sessions/(?P<id>\d+)/maketemplate/$',views.plannedsession_totemplate_view,
name='plannedsession_totemplate_view'),
re_path(r'^sessions/(?P<id>\d+)/togarmin/$',views.plannedsession_togarmin_view,
name='plannedsession_togarmin_view'),
re_path(r'^sessions/(?P<id>\d+)/compare/$',
views.plannedsession_compare_view,
name='plannedsession_compare_view'),

View File

@@ -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 += '&nbsp;&nbsp;&nbsp;'
if html:
spaces += '&nbsp;&nbsp;&nbsp;'
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))

View File

@@ -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: # 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,
'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,