adding simple export to Garmin
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user