Private
Public Access
1
0

adding simple export to Garmin

This commit is contained in:
Sander Roosendaal
2021-05-16 15:33:03 +02:00
parent 54190ed383
commit cbf7c4ab0b
9 changed files with 92 additions and 15 deletions

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

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

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

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