Merge branch 'feature/garmin' into develop
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
# cache
|
# cache
|
||||||
/django_cache/
|
/django_cache/
|
||||||
|
testcert.crt
|
||||||
|
testcert.key
|
||||||
|
|
||||||
# Compiled python modules.
|
# Compiled python modules.
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|||||||
+2
-1
@@ -57,7 +57,8 @@ class RowerInline(admin.StackedInline):
|
|||||||
'stravatoken','stravatokenexpirydate','stravarefreshtoken',
|
'stravatoken','stravatokenexpirydate','stravarefreshtoken',
|
||||||
'stravaexportas','strava_auto_export',
|
'stravaexportas','strava_auto_export',
|
||||||
'strava_auto_import',
|
'strava_auto_import',
|
||||||
'runkeepertoken','runkeeper_auto_export',)}),
|
'runkeepertoken','runkeeper_auto_export',
|
||||||
|
'garmintoken','garminrefreshtoken')}),
|
||||||
('Team',
|
('Team',
|
||||||
{'fields':('friends','privacy','team')}),
|
{'fields':('friends','privacy','team')}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -157,10 +157,6 @@ def add_stroke_data(user,c2id,workoutid,startdatetime,csvfilename,
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
def get_c2_workouts(rower):
|
def get_c2_workouts(rower):
|
||||||
|
|
||||||
if not ispromember(rower.user):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
thetoken = c2_open(rower.user)
|
thetoken = c2_open(rower.user)
|
||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
from rowers.imports import *
|
||||||
|
import datetime
|
||||||
|
import requests
|
||||||
|
from requests_oauthlib import OAuth1,OAuth1Session
|
||||||
|
from requests import Request, Session
|
||||||
|
import rowers.mytypes as mytypes
|
||||||
|
from rowers.mytypes import otwtypes
|
||||||
|
from rowers.rower_rules import is_workout_user,ispromember
|
||||||
|
from iso8601 import ParseError
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
import json
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
|
|
||||||
|
from rowsandall_app.settings import (
|
||||||
|
GARMIN_CLIENT_KEY, GARMIN_REDIRECT_URI, GARMIN_CLIENT_SECRET
|
||||||
|
)
|
||||||
|
|
||||||
|
from rowers.tasks import handle_c2_import_stroke_data, handle_c2_sync
|
||||||
|
import django_rq
|
||||||
|
queue = django_rq.get_queue('default')
|
||||||
|
queuelow = django_rq.get_queue('low')
|
||||||
|
queuehigh = django_rq.get_queue('low')
|
||||||
|
from rowers.utils import myqueue
|
||||||
|
from rowers.models import C2WorldClassAgePerformance,Rower,Workout,TombStone
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
|
||||||
|
from rowers.utils import custom_exception_handler,NoTokenError
|
||||||
|
from rowingdata import rowingdata
|
||||||
|
|
||||||
|
oauth_data = {
|
||||||
|
'client_id': GARMIN_CLIENT_KEY,
|
||||||
|
'client_secret': GARMIN_CLIENT_SECRET,
|
||||||
|
'redirect_uri': GARMIN_REDIRECT_URI,
|
||||||
|
'authorization_uri': "https://connectapi.garmin.com/oauth-service/oauth/request_token",
|
||||||
|
'content_type': 'application/x-www-form-urlencoded',
|
||||||
|
'tokenname': 'garmintoken',
|
||||||
|
'refreshtokenname': 'garminrefreshtoken',
|
||||||
|
'expirydatename': 'garmintokenexpirydate',
|
||||||
|
'bearer_auth': True,
|
||||||
|
'base_url': "https://connect.garmin.com/oauthConfirm",
|
||||||
|
'scope':'write',
|
||||||
|
'headers': 'Authorization: OAuth oauth_version="1.0"'
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = {
|
||||||
|
'startTimeInSeconds':'TimeStamp (sec)',
|
||||||
|
'latitudeInDegree':' latitude',
|
||||||
|
'longitudeInDegree':' longitude',
|
||||||
|
'heartRate':' HRCur (bpm)',
|
||||||
|
'speedMetersPerSecond':' AverageBoatSpeed (m/s)',
|
||||||
|
'totalDistanceInMeters':' Horizontal (meters)',
|
||||||
|
'clockDurationInSeconds':' ElapsedTime (sec)',
|
||||||
|
'powerInWatts':' Power (watts)',
|
||||||
|
'bikeCadenceInRPM':' Cadence (stokes/min)',
|
||||||
|
}
|
||||||
|
|
||||||
|
def garmin_authorize():
|
||||||
|
redirect_uri = oauth_data['redirect_uri']
|
||||||
|
client_secret = oauth_data['client_secret']
|
||||||
|
client_id = oauth_data['client_id']
|
||||||
|
base_uri = oauth_data['base_url']
|
||||||
|
|
||||||
|
|
||||||
|
garmin = OAuth1Session(oauth_data['client_id'],
|
||||||
|
client_secret=oauth_data['client_secret'],
|
||||||
|
)
|
||||||
|
fetch_response = garmin.fetch_request_token(oauth_data['authorization_uri'])
|
||||||
|
resource_owner_key = fetch_response.get('oauth_token')
|
||||||
|
resource_owner_secret = fetch_response.get('oauth_token_secret')
|
||||||
|
|
||||||
|
authorization_url = garmin.authorization_url(base_uri)
|
||||||
|
return authorization_url,resource_owner_key,resource_owner_secret
|
||||||
|
|
||||||
|
def garmin_processcallback(redirect_response,resource_owner_key,resource_owner_secret):
|
||||||
|
garmin = OAuth1Session(oauth_data['client_id'],
|
||||||
|
client_secret=oauth_data['client_secret'],
|
||||||
|
)
|
||||||
|
oauth_response = garmin.parse_authorization_response(redirect_response)
|
||||||
|
|
||||||
|
verifier = oauth_response.get('oauth_verifier')
|
||||||
|
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'],
|
||||||
|
resource_owner_key=resource_owner_key,
|
||||||
|
resource_owner_secret=resource_owner_secret,
|
||||||
|
verifier=verifier,)
|
||||||
|
oauth_tokens = garmin.fetch_access_token(access_token_url)
|
||||||
|
|
||||||
|
garmintoken = oauth_tokens.get('oauth_token')
|
||||||
|
garminrefreshtoken = oauth_tokens.get('oauth_token_secret')
|
||||||
|
|
||||||
|
return garmintoken,garminrefreshtoken
|
||||||
|
|
||||||
|
def garmin_open(user):
|
||||||
|
r = Rower.objects.get(user=user)
|
||||||
|
token = Rower.garmintoken
|
||||||
|
|
||||||
|
if (token == '') or (token is None):
|
||||||
|
raise NoTokenError("User has no garmin token")
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
def get_garmin_workout_list(user):
|
||||||
|
r = Rower.objects.get(user=user)
|
||||||
|
if (r.garmintoken == '') or (r.stravatoken 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,
|
||||||
|
)
|
||||||
|
|
||||||
|
url = 'https://healthapi.garmin.com/wellness-api/rest/activities?uploadStartTimeInSeconds=1593113760&uploadEndTimeInSeconds=1593279360'
|
||||||
|
|
||||||
|
result = garmin.get(url)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def garmin_getworkout(garminid,r,activity):
|
||||||
|
starttime = activity['startTimeInSeconds']
|
||||||
|
startdatetime = arrow.get(starttime)
|
||||||
|
durationseconds = activity['durationInSeconds']
|
||||||
|
duration = dataprep.totaltime_sec_to_string(durationseconds)
|
||||||
|
activitytype = activity['activityType']
|
||||||
|
name = 'Imported from Garmin'
|
||||||
|
date = startdatetime.date()
|
||||||
|
try:
|
||||||
|
distance = activity['distanceInMeters']
|
||||||
|
except KeyError:
|
||||||
|
distance = 0
|
||||||
|
try:
|
||||||
|
averagehr = activity['averageHeartRateInBeatsPerMinute']
|
||||||
|
maxhr = activity['maxHeartRateInBeatsPerMinute']
|
||||||
|
except KeyError:
|
||||||
|
averagehr = 0
|
||||||
|
maxhr = 0
|
||||||
|
try:
|
||||||
|
w = Workout.objects.get(uploadedtogarmin=garminid)
|
||||||
|
except Workout.DoesNotExist:
|
||||||
|
newcsvfile='media/garmin{code}_{importid}.csv'
|
||||||
|
w = Workout(user=r,csvfilename=newcsvfile)
|
||||||
|
w.startdatetime = datetime.datetime(
|
||||||
|
year=startdatetime.year,
|
||||||
|
month=startdatetime.month,
|
||||||
|
day=startdatetime.day,
|
||||||
|
hour=startdatetime.hour,
|
||||||
|
minute=startdatetime.minute,
|
||||||
|
second=startdatetime.second,
|
||||||
|
tzinfo=startdatetime.tzinfo)
|
||||||
|
w.starttime = startdatetime.time()
|
||||||
|
try:
|
||||||
|
w.duration = datetime.datetime.strptime(duration,"%H:%M:%S.%f").time()
|
||||||
|
except ValueError:
|
||||||
|
w.duration = datetime.datetime.strptime(duration,"%H:%M:%S")
|
||||||
|
try:
|
||||||
|
w.workouttype = mytypes.garminmappinginv[activitytype]
|
||||||
|
except KeyError:
|
||||||
|
w.workouttype = 'other'
|
||||||
|
w.name = name
|
||||||
|
w.date = date
|
||||||
|
w.distance = distance
|
||||||
|
w.uploadedtogarmin = garminid
|
||||||
|
|
||||||
|
w.save()
|
||||||
|
|
||||||
|
|
||||||
|
return w
|
||||||
|
|
||||||
|
def garmin_workouts_from_details(activities):
|
||||||
|
for activity in activities:
|
||||||
|
garmintoken = activity['userAccessToken']
|
||||||
|
try:
|
||||||
|
r = Rower.objects.get(garmintoken=garmintoken)
|
||||||
|
garminid = activity['summaryId'][:-7]
|
||||||
|
summary = activity['summary']
|
||||||
|
w = garmin_getworkout(garminid,r,summary)
|
||||||
|
samples = activity['samples']
|
||||||
|
df = pd.DataFrame(samples)
|
||||||
|
df.rename(columns=columns,inplace=True)
|
||||||
|
try:
|
||||||
|
pace = 500./df[' AverageBoatSpeed (m/s)']
|
||||||
|
except KeyError:
|
||||||
|
pace = 0
|
||||||
|
df[' AverageBoatSpeed (m/s)'] = 0
|
||||||
|
df[' Stroke500mPace (sec/500m)'] = pace
|
||||||
|
try:
|
||||||
|
spm = df[' Cadence (stokes/min)']
|
||||||
|
except KeyError:
|
||||||
|
df[' Cadence (stokes/min)'] = 0
|
||||||
|
df['cum_dist'] = df[' Horizontal (meters)']
|
||||||
|
try:
|
||||||
|
power = df[' Power (watts)']
|
||||||
|
except KeyError:
|
||||||
|
df[' Power (watts)'] = 0
|
||||||
|
df[' AverageDriveForce (lbs)'] = 0
|
||||||
|
df[' DriveLength (meters)'] = 0
|
||||||
|
df[' PeakDriveForce (lbs)'] = 1
|
||||||
|
df[' DriveTime (ms)'] = 0
|
||||||
|
|
||||||
|
rowdata = rowingdata(df=df)
|
||||||
|
rowdata.write_csv(w.csvfilename,gzip=True)
|
||||||
|
data = dataprep.dataprep(rowdata.df,id=w.id)
|
||||||
|
summary = rowdata.allstats()
|
||||||
|
w.summary=summary
|
||||||
|
w.uploadedtogarmin = garminid
|
||||||
|
w.save()
|
||||||
|
except Rower.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def garmin_workouts_from_summaries(activities):
|
||||||
|
for activity in activities:
|
||||||
|
garmintoken = activity['userAccessToken']
|
||||||
|
try:
|
||||||
|
r = Rower.objects.get(garmintoken=garmintoken)
|
||||||
|
id = activity['summaryId']
|
||||||
|
w = garmin_getworkout(id,r,activity)
|
||||||
|
except Rower.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return 1
|
||||||
@@ -296,7 +296,7 @@ class Command(BaseCommand):
|
|||||||
message.delete()
|
message.delete()
|
||||||
|
|
||||||
# Strava
|
# Strava
|
||||||
rowers = Rower.objects.filter(strava_auto_import=True).exclude(rowerplan='basic')
|
rowers = Rower.objects.filter(strava_auto_import=True)
|
||||||
for r in rowers:
|
for r in rowers:
|
||||||
stravastuff.get_strava_workouts(r)
|
stravastuff.get_strava_workouts(r)
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -837,6 +837,10 @@ class Rower(models.Model):
|
|||||||
polaruserid = models.IntegerField(default=0)
|
polaruserid = models.IntegerField(default=0)
|
||||||
polar_auto_import = models.BooleanField(default=False)
|
polar_auto_import = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
garmintoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
||||||
|
garminrefreshtoken = models.CharField(default='',max_length=1000,
|
||||||
|
blank=True,null=True)
|
||||||
|
|
||||||
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
||||||
stravatokenexpirydate = models.DateTimeField(blank=True,null=True)
|
stravatokenexpirydate = models.DateTimeField(blank=True,null=True)
|
||||||
stravarefreshtoken = models.CharField(default='',max_length=1000,
|
stravarefreshtoken = models.CharField(default='',max_length=1000,
|
||||||
@@ -2797,6 +2801,7 @@ class Workout(models.Model):
|
|||||||
uploadedtounderarmour = models.BigIntegerField(default=0)
|
uploadedtounderarmour = models.BigIntegerField(default=0)
|
||||||
uploadedtotp = models.BigIntegerField(default=0)
|
uploadedtotp = models.BigIntegerField(default=0)
|
||||||
uploadedtorunkeeper = models.BigIntegerField(default=0)
|
uploadedtorunkeeper = models.BigIntegerField(default=0)
|
||||||
|
uploadedtogarmin = models.BigIntegerField(default=0)
|
||||||
forceunit = models.CharField(default='lbs',
|
forceunit = models.CharField(default='lbs',
|
||||||
choices = (
|
choices = (
|
||||||
('lbs','lbs'),
|
('lbs','lbs'),
|
||||||
@@ -2837,12 +2842,13 @@ class Workout(models.Model):
|
|||||||
boattype = self.boattype
|
boattype = self.boattype
|
||||||
workouttype = self.workouttype
|
workouttype = self.workouttype
|
||||||
|
|
||||||
|
|
||||||
if workouttype != 'water':
|
if workouttype != 'water':
|
||||||
stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {workouttype} {ownerfirst} {ownerlast}'.format(
|
stri = u'{d} {n} {dist}m {duration} {workouttype} {ownerfirst} {ownerlast}'.format(
|
||||||
d = date.strftime('%Y-%m-%d'),
|
d = date.strftime('%Y-%m-%d'),
|
||||||
n = name,
|
n = name,
|
||||||
dist = distance,
|
dist = distance,
|
||||||
duration = duration,
|
duration = duration.strftime("%H:%M:%S"),
|
||||||
workouttype = workouttype,
|
workouttype = workouttype,
|
||||||
ownerfirst = ownerfirst,
|
ownerfirst = ownerfirst,
|
||||||
ownerlast = ownerlast,
|
ownerlast = ownerlast,
|
||||||
|
|||||||
+231
-186
@@ -42,207 +42,252 @@ workouttypes_ordered = collections.OrderedDict({
|
|||||||
|
|
||||||
workouttypes = tuple((key, value) for key, value in workouttypes_ordered.items())
|
workouttypes = tuple((key, value) for key, value in workouttypes_ordered.items())
|
||||||
|
|
||||||
|
def Reverse(tuples):
|
||||||
|
new_tup = tuples[::-1]
|
||||||
|
return new_tup
|
||||||
|
|
||||||
|
stravacollection = (
|
||||||
|
('water','Rowing'),
|
||||||
|
('rower','Rowing'),
|
||||||
|
('skierg','NordicSki'),
|
||||||
|
('Bike','Ride'),
|
||||||
|
('bikeerg','Ride'),
|
||||||
|
('dynamic','Rowing'),
|
||||||
|
('slides','Rowing'),
|
||||||
|
('paddle','StandUpPaddling'),
|
||||||
|
('snow','NordicSki'),
|
||||||
|
('coastal','Rowing'),
|
||||||
|
('c-boat','Rowing'),
|
||||||
|
('churchboat','Rowing'),
|
||||||
|
('Ride','Ride'),
|
||||||
|
('Run','Run'),
|
||||||
|
('NordicSki','NordicSki'),
|
||||||
|
('Swim','Swim'),
|
||||||
|
('Hike','Hike'),
|
||||||
|
('Walk','Walk'),
|
||||||
|
('Canoeing','Canoeing'),
|
||||||
|
('Crossfit','Crossfit'),
|
||||||
|
('StandUpPaddling','StandUpPaddling'),
|
||||||
|
('IceSkate','IceSkate'),
|
||||||
|
('WeightTraining','WeightTraining'),
|
||||||
|
('InlineSkate','InlineSkate'),
|
||||||
|
('Kayaking','Kayaking'),
|
||||||
|
('Workout','Workout'),
|
||||||
|
('Yoga','Yoga'),
|
||||||
|
('other','Workout'),
|
||||||
|
)
|
||||||
|
|
||||||
stravamapping = collections.OrderedDict({
|
stravamapping = {key:value for key,value in Reverse(stravacollection)}
|
||||||
'water':'Rowing',
|
|
||||||
'rower':'Rowing',
|
|
||||||
'skierg':'NordicSki',
|
|
||||||
'Bike':'Ride',
|
|
||||||
'bikeerg':'Ride',
|
|
||||||
'dynamic':'Rowing',
|
|
||||||
'slides':'Rowing',
|
|
||||||
'paddle':'StandUpPaddling',
|
|
||||||
'snow':'NordicSki',
|
|
||||||
'coastal':'Rowing',
|
|
||||||
'c-boat':'Rowing',
|
|
||||||
'churchboat':'Rowing',
|
|
||||||
'Ride':'Ride',
|
|
||||||
'Run':'Run',
|
|
||||||
'NordicSki':'NordicSki',
|
|
||||||
'Swim':'Swim',
|
|
||||||
'Hike':'Hike',
|
|
||||||
'Walk':'Walk',
|
|
||||||
'Canoeing':'Canoeing',
|
|
||||||
'Crossfit':'Crossfit',
|
|
||||||
'StandUpPaddling':'StandUpPaddling',
|
|
||||||
'IceSkate':'IceSkate',
|
|
||||||
'WeightTraining':'WeightTraining',
|
|
||||||
'InlineSkate':'InlineSkate',
|
|
||||||
'Kayaking':'Kayaking',
|
|
||||||
'Workout':'Workout',
|
|
||||||
'Yoga':'Yoga',
|
|
||||||
'other':'Workout',
|
|
||||||
|
|
||||||
})
|
garmincollection = (
|
||||||
|
('water','ROWING'),
|
||||||
|
('rower','INDOOR_ROWING'),
|
||||||
|
('skierg','CROSS_COUNTRY_SKIING'),
|
||||||
|
('Bike','ROAD_BIKING'),
|
||||||
|
('bikeerg','INDOOR_CYCLING'),
|
||||||
|
('dynamic','INDOOR_ROWING'),
|
||||||
|
('slides','INDOOR_ROWING'),
|
||||||
|
('paddle','PADDLING'),
|
||||||
|
('snow','CROSS_COUNTRY_SKIING'),
|
||||||
|
('coastal','ROWING'),
|
||||||
|
('c-boat','ROWING'),
|
||||||
|
('churchboat','ROWING'),
|
||||||
|
('Ride','ROAD_BIKING'),
|
||||||
|
('Run','RUNNING'),
|
||||||
|
('NordicSki','CROSS_COUNTRY_SKIING'),
|
||||||
|
('Swim','SWIMMING'),
|
||||||
|
('Hike','HIKING'),
|
||||||
|
('Walk','WALKING'),
|
||||||
|
('Canoeing','PADDLING'),
|
||||||
|
('Crossfit','FITNESS_EQUIPMENT'),
|
||||||
|
('StandUpPaddling','STAND_UP_PADDLEBOARDING'),
|
||||||
|
('IceSkate','SKATING'),
|
||||||
|
('WeightTraining','STRENGTH_TRAINING'),
|
||||||
|
('InlineSkate','INLINE_SKATING'),
|
||||||
|
('Kayaking','PADDLING'),
|
||||||
|
('Workout','OTHER'),
|
||||||
|
('Yoga','OTHER'),
|
||||||
|
('other','OTHER'),
|
||||||
|
)
|
||||||
|
|
||||||
stmapping = collections.OrderedDict({
|
garminmapping = {key:value for key,value in Reverse(garmincollection)}
|
||||||
'water':'Rowing',
|
|
||||||
'rower':'Rowing',
|
|
||||||
'skierg':'Skiing:Nordic',
|
|
||||||
'Bike':'Cycling',
|
|
||||||
'bikeerg':'Cycling',
|
|
||||||
'dynamic':'Rowing',
|
|
||||||
'slides':'Rowing',
|
|
||||||
'paddle':'Other:Paddling',
|
|
||||||
'snow':'Skiing:Nordic',
|
|
||||||
'coastal':'Rowing',
|
|
||||||
'c-boat':'Rowing',
|
|
||||||
'churchboat':'Rowing',
|
|
||||||
'Ride':'Cycling',
|
|
||||||
'Run':'Running',
|
|
||||||
'NordicSki':'Skiing:Nordic',
|
|
||||||
'Swim':'Swimming',
|
|
||||||
'Hike':'Hiking',
|
|
||||||
'RollerSki':'Other:RollerSki',
|
|
||||||
'Walk':'Other:Walk',
|
|
||||||
'Canoeing':'Other:Canoeing',
|
|
||||||
'Crossfit':'Other:Crossfit',
|
|
||||||
'StandUpPaddling':'Other:StandUpPaddling',
|
|
||||||
'IceSkate':'Skating',
|
|
||||||
'WeightTraining':'Other:WeightTraining',
|
|
||||||
'InlineSkate':'Skating:InlineSkate',
|
|
||||||
'Kayaking':'Other:Kayaking',
|
|
||||||
'Workout':'Other:Workout',
|
|
||||||
'Yoga':'Other',
|
|
||||||
'other':'Other',
|
|
||||||
|
|
||||||
})
|
stcollection = (
|
||||||
|
('water','Rowing'),
|
||||||
|
('rower','Rowing'),
|
||||||
|
('skierg','Skiing:Nordic'),
|
||||||
|
('Bike','Cycling'),
|
||||||
|
('bikeerg','Cycling'),
|
||||||
|
('dynamic','Rowing'),
|
||||||
|
('slides','Rowing'),
|
||||||
|
('paddle','Other:Paddling'),
|
||||||
|
('snow','Skiing:Nordic'),
|
||||||
|
('coastal','Rowing'),
|
||||||
|
('c-boat','Rowing'),
|
||||||
|
('churchboat','Rowing'),
|
||||||
|
('Ride','Cycling'),
|
||||||
|
('Run','Running'),
|
||||||
|
('NordicSki','Skiing:Nordic'),
|
||||||
|
('Swim','Swimming'),
|
||||||
|
('Hike','Hiking'),
|
||||||
|
('RollerSki','Other:RollerSki'),
|
||||||
|
('Walk','Other:Walk'),
|
||||||
|
('Canoeing','Other:Canoeing'),
|
||||||
|
('Crossfit','Other:Crossfit'),
|
||||||
|
('StandUpPaddling','Other:StandUpPaddling'),
|
||||||
|
('IceSkate','Skating'),
|
||||||
|
('WeightTraining','Other:WeightTraining'),
|
||||||
|
('InlineSkate','Skating:InlineSkate'),
|
||||||
|
('Kayaking','Other:Kayaking'),
|
||||||
|
('Workout','Other:Workout'),
|
||||||
|
('Yoga','Other'),
|
||||||
|
('other','Other'),
|
||||||
|
)
|
||||||
|
|
||||||
rkmapping = collections.OrderedDict({
|
stmapping = {key:value for key,value in Reverse(stcollection)}
|
||||||
'water':'Rowing',
|
|
||||||
'rower':'Rowing',
|
|
||||||
'skierg':'Cross-Country Skiing',
|
|
||||||
'Bike':'Cycling',
|
|
||||||
'bikeerg':'Cycling',
|
|
||||||
'dynamic':'Rowing',
|
|
||||||
'slides':'Rowing',
|
|
||||||
'paddle':'Other:Paddling',
|
|
||||||
'snow':'Cross-Country Skiing',
|
|
||||||
'coastal':'Rowing',
|
|
||||||
'c-boat':'Rowing',
|
|
||||||
'churchboat':'Rowing',
|
|
||||||
'Ride':'Cycling',
|
|
||||||
'Run':'Running',
|
|
||||||
'NordicSki':'Cross-Country Skiing',
|
|
||||||
'Swim':'Swimming',
|
|
||||||
'Hike':'Hiking',
|
|
||||||
'Walk':'Walking',
|
|
||||||
'Canoeing':'Other',
|
|
||||||
'Crossfit':'CrossFit',
|
|
||||||
'StandUpPaddling':'Other',
|
|
||||||
'IceSkate':'Skating',
|
|
||||||
'WeightTraining':'Other',
|
|
||||||
'InlineSkate':'Skating',
|
|
||||||
'Kayaking':'Other',
|
|
||||||
'Workout':'Other',
|
|
||||||
'other':'Other',
|
|
||||||
'Yoga':'Other',
|
|
||||||
})
|
|
||||||
|
|
||||||
polarmapping = collections.OrderedDict({
|
rkcollection = (
|
||||||
'water':'Rowing',
|
('water','Rowing'),
|
||||||
'rower':'Rowing',
|
('rower','Rowing'),
|
||||||
'skierg':'Skiing',
|
('skierg','Cross-Country Skiing'),
|
||||||
'Bike':'Cycling',
|
('Bike','Cycling'),
|
||||||
'bikeerg':'Cycling',
|
('bikeerg','Cycling'),
|
||||||
'dynamic':'Rowing',
|
('dynamic','Rowing'),
|
||||||
'slides':'Rowing',
|
('slides','Rowing'),
|
||||||
'paddle':'Other Outdoor',
|
('paddle','Other:Paddling'),
|
||||||
'snow':'Skiing',
|
('snow','Cross-Country Skiing'),
|
||||||
'coastal':'Rowing',
|
('coastal','Rowing'),
|
||||||
'c-boat':'Rowing',
|
('c-boat','Rowing'),
|
||||||
'churchboat':'Rowing',
|
('churchboat','Rowing'),
|
||||||
'Ride':'Cycling',
|
('Ride','Cycling'),
|
||||||
'Run':'Running',
|
('Run','Running'),
|
||||||
'NordicSki':'Skiing',
|
('NordicSki','Cross-Country Skiing'),
|
||||||
'Swim':'Swimming',
|
('Swim','Swimming'),
|
||||||
'Hike':'Hiking',
|
('Hike','Hiking'),
|
||||||
'Walk':'Walking',
|
('Walk','Walking'),
|
||||||
'Canoeing':'Canoeing',
|
('Canoeing','Other'),
|
||||||
'Crossfit':'Crossfit',
|
('Crossfit','CrossFit'),
|
||||||
'StandUpPaddling':'Other Outdoor',
|
('StandUpPaddling','Other'),
|
||||||
'IceSkate':'Skating',
|
('IceSkate','Skating'),
|
||||||
'WeightTraining':'Strength training',
|
('WeightTraining','Other'),
|
||||||
'InlineSkate':'Skating',
|
('InlineSkate','Skating'),
|
||||||
'Kayaking':'Kayaking',
|
('Kayaking','Other'),
|
||||||
'Workout':'Other Indoor',
|
('Workout','Other'),
|
||||||
'other':'Other Indoor',
|
('other','Other'),
|
||||||
'Yoga':'Yoga',
|
('Yoga','Other'),
|
||||||
})
|
)
|
||||||
|
|
||||||
tpmapping = collections.OrderedDict({
|
rkmapping = {key:value for key,value in Reverse(rkcollection)}
|
||||||
'water':'rowing',
|
|
||||||
'rower':'rowing',
|
|
||||||
'skierg':'xc-ski',
|
|
||||||
'Bike':'bike',
|
|
||||||
'Bikeerg':'bike',
|
|
||||||
'dynamic':'rowing',
|
|
||||||
'slides':'rowing',
|
|
||||||
'paddle':'other',
|
|
||||||
'snow':'xc-ski',
|
|
||||||
'coastal':'rowing',
|
|
||||||
'c-boat':'rowing',
|
|
||||||
'churchboat':'rowing',
|
|
||||||
'Ride':'cycling',
|
|
||||||
'Run':'run',
|
|
||||||
'NordicSki':'xc-ski',
|
|
||||||
'Swim':'swim',
|
|
||||||
'Hike':'other',
|
|
||||||
'Walk':'walk',
|
|
||||||
'Canoeing':'other',
|
|
||||||
'Crossfit':'other',
|
|
||||||
'StandUpPaddling':'other',
|
|
||||||
'IceSkate':'other',
|
|
||||||
'WeightTraining':'strength',
|
|
||||||
'InlineSkate':'other',
|
|
||||||
'Kayaking':'other',
|
|
||||||
'Workout':'other',
|
|
||||||
'other':'other',
|
|
||||||
'Yoga':'other',
|
|
||||||
})
|
|
||||||
|
|
||||||
c2mapping = collections.OrderedDict({
|
polarcollection = (
|
||||||
'water':'water',
|
('water','Rowing'),
|
||||||
'rower':'rower',
|
('rower','Rowing'),
|
||||||
'skierg':'skierg',
|
('skierg','Skiing'),
|
||||||
'Bike':'bike',
|
('Bike','Cycling'),
|
||||||
'bikeerg':'bike',
|
('bikeerg','Cycling'),
|
||||||
'dynamic':'dynamic',
|
('dynamic','Rowing'),
|
||||||
'slides':'slides',
|
('slides','Rowing'),
|
||||||
'paddle':'paddle',
|
('paddle','Other Outdoor'),
|
||||||
'snow':'snow',
|
('snow','Skiing'),
|
||||||
'coastal':'water',
|
('coastal','Rowing'),
|
||||||
'c-boat':'water',
|
('c-boat','Rowing'),
|
||||||
'churchboat':'water',
|
('churchboat','Rowing'),
|
||||||
'Ride':'bike',
|
('Ride','Cycling'),
|
||||||
'Run':None,
|
('Run','Running'),
|
||||||
'NordicSki':'snow',
|
('NordicSki','Skiing'),
|
||||||
'Swim':None,
|
('Swim','Swimming'),
|
||||||
'Hike':None,
|
('Hike','Hiking'),
|
||||||
'Walk':None,
|
('Walk','Walking'),
|
||||||
'Canoeing':'paddle',
|
('Canoeing','Canoeing'),
|
||||||
'Crossfit':None,
|
('Crossfit','Crossfit'),
|
||||||
'StandUpPaddling':None,
|
('StandUpPaddling','Other Outdoor'),
|
||||||
'IceSkate':None,
|
('IceSkate','Skating'),
|
||||||
'WeightTraining':None,
|
('WeightTraining','Strength training'),
|
||||||
'InlineSkate':None,
|
('InlineSkate','Skating'),
|
||||||
'Kayaking':None,
|
('Kayaking','Kayaking'),
|
||||||
'Workout':None,
|
('Workout','Other Indoor'),
|
||||||
'other':None,
|
('other','Other Indoor'),
|
||||||
'Yoga':None,
|
('Yoga','Yoga'),
|
||||||
|
)
|
||||||
|
|
||||||
})
|
polarmapping = {key:value for key,value in Reverse(polarcollection)}
|
||||||
|
|
||||||
c2mappinginv = {value:key for key,value in reversed(c2mapping.items()) if value is not None}
|
tpcollection = (
|
||||||
|
('water','rowing'),
|
||||||
|
('rower','rowing'),
|
||||||
|
('skierg','xc-ski'),
|
||||||
|
('Bike','bike'),
|
||||||
|
('Bikeerg','bike'),
|
||||||
|
('dynamic','rowing'),
|
||||||
|
('slides','rowing'),
|
||||||
|
('paddle','other'),
|
||||||
|
('snow','xc-ski'),
|
||||||
|
('coastal','rowing'),
|
||||||
|
('c-boat','rowing'),
|
||||||
|
('churchboat','rowing'),
|
||||||
|
('Ride','cycling'),
|
||||||
|
('Run','run'),
|
||||||
|
('NordicSki','xc-ski'),
|
||||||
|
('Swim','swim'),
|
||||||
|
('Hike','other'),
|
||||||
|
('Walk','walk'),
|
||||||
|
('Canoeing','other'),
|
||||||
|
('Crossfit','other'),
|
||||||
|
('StandUpPaddling','other'),
|
||||||
|
('IceSkate','other'),
|
||||||
|
('WeightTraining','strength'),
|
||||||
|
('InlineSkate','other'),
|
||||||
|
('Kayaking','other'),
|
||||||
|
('Workout','other'),
|
||||||
|
('other','other'),
|
||||||
|
('Yoga','other'),
|
||||||
|
)
|
||||||
|
|
||||||
stravamappinginv = {value:key for key,value in reversed(stravamapping.items()) if value is not None}
|
tpmapping = {key:value for key,value in Reverse(tpcollection)}
|
||||||
|
|
||||||
stmappinginv = {value:key for key,value in reversed(stmapping.items()) if value is not None}
|
c2collection = (
|
||||||
|
('water','water'),
|
||||||
|
('rower','rower'),
|
||||||
|
('skierg','skierg'),
|
||||||
|
('Bike','bike'),
|
||||||
|
('bikeerg','bike'),
|
||||||
|
('dynamic','dynamic'),
|
||||||
|
('slides','slides'),
|
||||||
|
('paddle','paddle'),
|
||||||
|
('snow','snow'),
|
||||||
|
('coastal','water'),
|
||||||
|
('c-boat','water'),
|
||||||
|
('churchboat','water'),
|
||||||
|
('Ride','bike'),
|
||||||
|
('Run',None),
|
||||||
|
('NordicSki','snow'),
|
||||||
|
('Swim',None),
|
||||||
|
('Hike',None),
|
||||||
|
('Walk',None),
|
||||||
|
('Canoeing','paddle'),
|
||||||
|
('Crossfit',None),
|
||||||
|
('StandUpPaddling',None),
|
||||||
|
('IceSkate',None),
|
||||||
|
('WeightTraining',None),
|
||||||
|
('InlineSkate',None),
|
||||||
|
('Kayaking',None),
|
||||||
|
('Workout',None),
|
||||||
|
('other',None),
|
||||||
|
('Yoga',None),
|
||||||
|
)
|
||||||
|
|
||||||
rkmappinginv = {value:key for key,value in reversed(rkmapping.items()) if value is not None}
|
c2mapping = {key:value for key,value in Reverse(c2collection)}
|
||||||
|
c2mappinginv = {value:key for key,value in Reverse(c2collection) if value is not None}
|
||||||
|
|
||||||
polarmappinginv = {value:key for key,value in reversed(polarmapping.items()) if value is not None}
|
stravamappinginv = {value:key for key,value in Reverse(stravacollection) if value is not None}
|
||||||
|
|
||||||
|
stmappinginv = {value:key for key,value in Reverse(stcollection) if value is not None}
|
||||||
|
|
||||||
|
rkmappinginv = {value:key for key,value in Reverse(rkcollection) if value is not None}
|
||||||
|
|
||||||
|
polarmappinginv = {value:key for key,value in Reverse(polarcollection) if value is not None}
|
||||||
|
|
||||||
|
garminmappinginv = {value:key for key, value in Reverse(garmincollection) if value is not None}
|
||||||
|
|
||||||
otwtypes = (
|
otwtypes = (
|
||||||
'water',
|
'water',
|
||||||
|
|||||||
@@ -126,10 +126,6 @@ def get_strava_workout_list(user,limit_n=0):
|
|||||||
|
|
||||||
# gets all new Strava workouts for a rower
|
# gets all new Strava workouts for a rower
|
||||||
def get_strava_workouts(rower):
|
def get_strava_workouts(rower):
|
||||||
|
|
||||||
if not ispromember(rower.user):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
thetoken = strava_open(rower.user)
|
thetoken = strava_open(rower.user)
|
||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
|
|||||||
@@ -220,6 +220,17 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
|
<li id="export-garmin">
|
||||||
|
{% if workout.uploadedtogarmin %}
|
||||||
|
<a href="https://connect.garmin.com/modern/activity/{{ workout.uploadedtogarmin }}">
|
||||||
|
Garmin <i class="fas fa-check"></i>
|
||||||
|
</a>
|
||||||
|
{% elif user.rower.garmintoken == None or user.rower.garmintoken == '' %}
|
||||||
|
<a href="/rowers/me/garminauthorize/">
|
||||||
|
Connect to Garmin
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
<li id="export-csv">
|
<li id="export-csv">
|
||||||
<a href="/rowers/workout/{{ workout.id|encode }}/emailcsv/">
|
<a href="/rowers/workout/{{ workout.id|encode }}/emailcsv/">
|
||||||
CSV
|
CSV
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Automatic Synchronization with other fitness sites</td>
|
<td>Automatic Synchronization with other fitness sites</td>
|
||||||
<td> </td>
|
<td>✔</td>
|
||||||
<td>✔</td>
|
<td>✔</td>
|
||||||
<td>✔</td>
|
<td>✔</td>
|
||||||
<td>✔</td>
|
<td>✔</td>
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
<h1>Import and Export Settings for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
|
<h1>Import and Export Settings for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
|
||||||
|
|
||||||
{% if user.rower.rowerplan == 'basic' %}
|
|
||||||
The auto import and export settings only work on <a href="/rowers/paidplans/">a paid plan</a>.
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<p style="color: red;">
|
<p style="color: red;">
|
||||||
Please correct the error{{ form.errors|pluralize }} below.
|
Please correct the error{{ form.errors|pluralize }} below.
|
||||||
@@ -23,8 +19,12 @@ The auto import and export settings only work on <a href="/rowers/paidplans/">a
|
|||||||
<input type="submit" value="Save">
|
<input type="submit" value="Save">
|
||||||
</form>
|
</form>
|
||||||
</p>
|
</p>
|
||||||
|
<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).
|
||||||
|
</p>
|
||||||
<p>Click on one of the icons below to connect to the service of your
|
<p>Click on one of the icons below to connect to the service of your
|
||||||
choice or to renew the authorization</p>
|
choice or to renew the authorization.</p>
|
||||||
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
|
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
|
||||||
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
|
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
|
||||||
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
|
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
|
||||||
@@ -34,6 +34,8 @@ The auto import and export settings only work on <a href="/rowers/paidplans/">a
|
|||||||
alt="connect with Polar" width="130"></a></p>
|
alt="connect with Polar" width="130"></a></p>
|
||||||
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png"
|
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png"
|
||||||
alt="connect with Polar" width="130"></a></p>
|
alt="connect with Polar" width="130"></a></p>
|
||||||
|
<p><a href="/rowers/me/garminauthorize"><img src="/static/img/garmin_badge_130.png"
|
||||||
|
alt="connect with Garmin" with="130"></a></p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,98 @@ from __future__ import division
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
#from __future__ import print_function
|
#from __future__ import print_function
|
||||||
from .statements import *
|
from .statements import *
|
||||||
nu = datetime.datetime.now()
|
nu = datetime.datetime.now()
|
||||||
|
|
||||||
|
|
||||||
import rowers
|
import rowers
|
||||||
|
from rowers import dataprep
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@override_settings(TESTING=True)
|
||||||
|
class GarminObjects(DjangoTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.c = Client()
|
||||||
|
self.u = User.objects.create_user('john',
|
||||||
|
'sander@ds.ds',
|
||||||
|
'koeinsloot')
|
||||||
|
|
||||||
|
self.u.first_name = 'John'
|
||||||
|
self.u.last_name = 'Sander'
|
||||||
|
self.u.save()
|
||||||
|
self.r = Rower.objects.create(user=self.u,gdproptin=True,surveydone=True,
|
||||||
|
gdproptindate=timezone.now()
|
||||||
|
)
|
||||||
|
self.r.garmintoken = 'dfdzf'
|
||||||
|
self.r.garminrefreshtoken = 'fsls'
|
||||||
|
self.r.save()
|
||||||
|
self.c.login(username='john',password='koeinsloot')
|
||||||
|
|
||||||
|
self.nu = datetime.datetime.now()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
ws = Workout.objects.filter(user=self.r)
|
||||||
|
for w in ws:
|
||||||
|
w.delete()
|
||||||
|
|
||||||
|
def test_garmin_push_summaries(self):
|
||||||
|
data = json.load(open('rowers/tests/testdata/garminsummarydata.txt','r'))
|
||||||
|
response = self.c.post('/rowers/garmin/summaries/',json.dumps(data),
|
||||||
|
content_type="application/json")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
#response = self.c.get('/rowers/workout/'+encoded1+'/', follow=True)
|
||||||
|
#self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
ws = Workout.objects.filter(user=self.r)
|
||||||
|
self.assertEqual(ws.count(),3)
|
||||||
|
|
||||||
|
def test_garmin_push_details3(self):
|
||||||
|
data = json.load(open('rowers/tests/testdata/garmindetail3.txt','r'))
|
||||||
|
response = self.c.post('/rowers/garmin/activities/',json.dumps(data),
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
ws = Workout.objects.filter(user=self.r)
|
||||||
|
self.assertEqual(ws.count(),1)
|
||||||
|
|
||||||
|
data,w = dataprep.getrowdata_db(id=ws[0].id)
|
||||||
|
|
||||||
|
self.assertEqual(len(data),515)
|
||||||
|
|
||||||
|
def test_garmin_push_details2(self):
|
||||||
|
data = json.load(open('rowers/tests/testdata/garmindetail2.txt','r'))
|
||||||
|
response = self.c.post('/rowers/garmin/activities/',json.dumps(data),
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
ws = Workout.objects.filter(user=self.r)
|
||||||
|
self.assertEqual(ws.count(),3)
|
||||||
|
|
||||||
|
data,w = dataprep.getrowdata_db(id=ws[0].id)
|
||||||
|
self.assertEqual(len(data),451)
|
||||||
|
|
||||||
|
def test_garmin_push_details1(self):
|
||||||
|
data = json.load(open('rowers/tests/testdata/garmindetail1.txt','r'))
|
||||||
|
response = self.c.post('/rowers/garmin/activities/',json.dumps(data),
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
response = self.c.get('/rowers/workout/'+encoded1+'/', follow=True)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
ws = Workout.objects.filter(user=self.r)
|
||||||
|
self.assertEqual(ws.count(),2)
|
||||||
|
|
||||||
|
data,w = dataprep.getrowdata_db(id=ws[0].id)
|
||||||
|
self.assertEqual(len(data),2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@override_settings(TESTING=True)
|
@override_settings(TESTING=True)
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
[ { "userId": "25858854-f086-4026-83ac-a3bfc97dcbb1",
|
||||||
|
"userAccessToken": "dfdzf",
|
||||||
|
"summaryId" : "14098044-detail",
|
||||||
|
"summary" : {
|
||||||
|
"durationInSeconds" : 4828,
|
||||||
|
"startTimeInSeconds" : 1593691200,
|
||||||
|
"startTimeOffsetInSeconds" : -18000,
|
||||||
|
"activityType" : "WALKING",
|
||||||
|
"averageHeartRateInBeatsPerMinute" : 90,
|
||||||
|
"averageRunCadenceInStepsPerMinute" : 38.0,
|
||||||
|
"averageSpeedInMetersPerSecond" : 0.8794985,
|
||||||
|
"averagePaceInMinutesPerKilometer" : 18.247978,
|
||||||
|
"activeKilocalories" : 233,
|
||||||
|
"distanceInMeters" : 4103.25,
|
||||||
|
"maxHeartRateInBeatsPerMinute" : 124,
|
||||||
|
"maxPaceInMinutesPerKilometer" : 3.2660666,
|
||||||
|
"maxRunCadenceInStepsPerMinute" : 120.0,
|
||||||
|
"maxSpeedInMetersPerSecond" : 4.289388,
|
||||||
|
"steps" : 1623,
|
||||||
|
"totalElevationGainInMeters" : 20.09
|
||||||
|
},
|
||||||
|
"samples" : [ {
|
||||||
|
"startTimeInSeconds" : 1593691200,
|
||||||
|
"speedMetersPerSecond" : 0.0,
|
||||||
|
"totalDistanceInMeters" : 25.0,
|
||||||
|
"timerDurationInSeconds" : 23,
|
||||||
|
"clockDurationInSeconds" : 30,
|
||||||
|
"movingDurationInSeconds" : 0
|
||||||
|
}, {
|
||||||
|
"startTimeInSeconds" : 1593691200,
|
||||||
|
"speedMetersPerSecond" : 0.0,
|
||||||
|
"totalDistanceInMeters" : 27.0,
|
||||||
|
"timerDurationInSeconds" : 23,
|
||||||
|
"clockDurationInSeconds" : 27,
|
||||||
|
"movingDurationInSeconds" : 0
|
||||||
|
} ]
|
||||||
|
}, {
|
||||||
|
"userId": "25858854-f086-4026-83ac-a3bfc97dcbb1",
|
||||||
|
"userAccessToken": "dfdzf",
|
||||||
|
"summaryId" : "14033650-detail",
|
||||||
|
"summary" : {
|
||||||
|
"durationInSeconds" : 4778,
|
||||||
|
"startTimeInSeconds" : 1593777600,
|
||||||
|
"startTimeOffsetInSeconds" : -18000,
|
||||||
|
"activityType" : "WALKING",
|
||||||
|
"averageHeartRateInBeatsPerMinute" : 86,
|
||||||
|
"averageRunCadenceInStepsPerMinute" : 30.0,
|
||||||
|
"averageSpeedInMetersPerSecond" : 0.6164423,
|
||||||
|
"averagePaceInMinutesPerKilometer" : 18.036388,
|
||||||
|
"activeKilocalories" : 182,
|
||||||
|
"distanceInMeters" : 3742.63,
|
||||||
|
"maxHeartRateInBeatsPerMinute" : 120,
|
||||||
|
"maxPaceInMinutesPerKilometer" : 3.5400813,
|
||||||
|
"maxRunCadenceInStepsPerMinute" : 118.0,
|
||||||
|
"maxSpeedInMetersPerSecond" : 4.5140486,
|
||||||
|
"steps" : 1623,
|
||||||
|
"totalElevationGainInMeters" : 20.9
|
||||||
|
},
|
||||||
|
"samples" : [ {
|
||||||
|
"startTimeInSeconds" : 1593691200,
|
||||||
|
"speedMetersPerSecond" : 1.0,
|
||||||
|
"totalDistanceInMeters" : 27.0,
|
||||||
|
"timerDurationInSeconds" : 27,
|
||||||
|
"clockDurationInSeconds" : 27,
|
||||||
|
"movingDurationInSeconds" : 0
|
||||||
|
}, {
|
||||||
|
"startTimeInSeconds" : 1593691200,
|
||||||
|
"speedMetersPerSecond" : 1.0,
|
||||||
|
"totalDistanceInMeters" : 24.0,
|
||||||
|
"timerDurationInSeconds" : 23,
|
||||||
|
"clockDurationInSeconds" : 29,
|
||||||
|
"movingDurationInSeconds" : 0
|
||||||
|
} ]
|
||||||
|
} ]
|
||||||
+4
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
+43
@@ -0,0 +1,43 @@
|
|||||||
|
{"activities":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"userId": "25858854-f086-4026-83ac-a3bfc97dcbb1",
|
||||||
|
"userAccessToken": "dfdzf",
|
||||||
|
"summaryId" : "5180795946",
|
||||||
|
"durationInSeconds" : 640,
|
||||||
|
"startTimeInSeconds" : 1593758587,
|
||||||
|
"startTimeOffsetInSeconds" : 7200,
|
||||||
|
"activityType" :
|
||||||
|
"INDOOR_ROWING",
|
||||||
|
"averageHeartRateInBeatsPerMinute" : 96,
|
||||||
|
"activeKilocalories" : 55,
|
||||||
|
"deviceName" : "vivoactive3",
|
||||||
|
"maxHeartRateInBeatsPerMinute" : 105},
|
||||||
|
{
|
||||||
|
"userId": "25858854-f086-4026-83ac-a3bfc97dcbb1",
|
||||||
|
"userAccessToken": "dfdzf",
|
||||||
|
"summaryId" : "5180798644",
|
||||||
|
"durationInSeconds" : 3959,
|
||||||
|
"startTimeInSeconds" : 1593759504,
|
||||||
|
"startTimeOffsetInSeconds" : 7200,
|
||||||
|
"activityType" : "STRENGTH_TRAINING",
|
||||||
|
"averageHeartRateInBeatsPerMinute" : 97,
|
||||||
|
"activeKilocalories" : 296,
|
||||||
|
"deviceName" : "vivoactive3",
|
||||||
|
"maxHeartRateInBeatsPerMinute" : 132,
|
||||||
|
"steps" : 630},
|
||||||
|
{
|
||||||
|
"userId": "25858854-f086-4026-83ac-a3bfc97dcbb1",
|
||||||
|
"userAccessToken": "dfdzf",
|
||||||
|
"summaryId" : "5180799021",
|
||||||
|
"durationInSeconds" : 309,
|
||||||
|
"startTimeInSeconds" : 1593763505,
|
||||||
|
"startTimeOffsetInSeconds" : 7200,
|
||||||
|
"activityType" : "INDOOR_CYCLING",
|
||||||
|
"averageHeartRateInBeatsPerMinute" : 109,
|
||||||
|
"activeKilocalories" : 38,
|
||||||
|
"deviceName" : "vivoactive3",
|
||||||
|
"maxHeartRateInBeatsPerMinute" : 119
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+6
-6
@@ -535,7 +535,7 @@ def do_sync(w,options, quick=False):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if ('upload_to_C2' in options and options['upload_to_C2']) or (w.user.c2_auto_export and ispromember(w.user.user)):
|
if ('upload_to_C2' in options and options['upload_to_C2']) or (w.user.c2_auto_export):
|
||||||
try:
|
try:
|
||||||
message,id = c2stuff.workout_c2_upload(w.user.user,w,asynchron=True)
|
message,id = c2stuff.workout_c2_upload(w.user.user,w,asynchron=True)
|
||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
@@ -544,7 +544,7 @@ def do_sync(w,options, quick=False):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if ('upload_to_Strava' in options and upload_to_strava) or (w.user.strava_auto_export and ispromember(w.user.user)):
|
if ('upload_to_Strava' in options and upload_to_strava) or (w.user.strava_auto_export):
|
||||||
try:
|
try:
|
||||||
message,id = stravastuff.workout_strava_upload(
|
message,id = stravastuff.workout_strava_upload(
|
||||||
w.user.user,w,quick=quick,asynchron=True,
|
w.user.user,w,quick=quick,asynchron=True,
|
||||||
@@ -563,7 +563,7 @@ def do_sync(w,options, quick=False):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and ispromember(w.user.user)):
|
if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export):
|
||||||
try:
|
try:
|
||||||
message,id = sporttracksstuff.workout_sporttracks_upload(
|
message,id = sporttracksstuff.workout_sporttracks_upload(
|
||||||
w.user.user,w,asynchron=True,
|
w.user.user,w,asynchron=True,
|
||||||
@@ -573,7 +573,7 @@ def do_sync(w,options, quick=False):
|
|||||||
id = 0
|
id = 0
|
||||||
|
|
||||||
|
|
||||||
if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export and ispromember(w.user.user)):
|
if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export):
|
||||||
try:
|
try:
|
||||||
message,id = runkeeperstuff.workout_runkeeper_upload(
|
message,id = runkeeperstuff.workout_runkeeper_upload(
|
||||||
w.user.user,w,asynchron=True,
|
w.user.user,w,asynchron=True,
|
||||||
@@ -582,7 +582,7 @@ def do_sync(w,options, quick=False):
|
|||||||
message = "Please connect to Runkeeper first"
|
message = "Please connect to Runkeeper first"
|
||||||
id = 0
|
id = 0
|
||||||
|
|
||||||
if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export and ispromember(w.user.user)):
|
if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export):
|
||||||
try:
|
try:
|
||||||
message,id = underarmourstuff.workout_ua_upload(
|
message,id = underarmourstuff.workout_ua_upload(
|
||||||
w.user.user,w
|
w.user.user,w
|
||||||
@@ -592,7 +592,7 @@ def do_sync(w,options, quick=False):
|
|||||||
id = 0
|
id = 0
|
||||||
|
|
||||||
|
|
||||||
if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export and ispromember(w.user.user)):
|
if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export):
|
||||||
try:
|
try:
|
||||||
message,id = tpstuff.workout_tp_upload(
|
message,id = tpstuff.workout_tp_upload(
|
||||||
w.user.user,w
|
w.user.user,w
|
||||||
|
|||||||
@@ -415,6 +415,8 @@ urlpatterns = [
|
|||||||
re_path(r'^workout/(?P<pk>\b[0-9A-Fa-f]+\b)/delete/$',login_required(
|
re_path(r'^workout/(?P<pk>\b[0-9A-Fa-f]+\b)/delete/$',login_required(
|
||||||
views.WorkoutDelete.as_view()),
|
views.WorkoutDelete.as_view()),
|
||||||
name='workout_delete'),
|
name='workout_delete'),
|
||||||
|
re_path(r'^garmin/summaries/',views.garmin_summaries_view,name='garmin_summaries_view'),
|
||||||
|
re_path(r'^garmin/activities/',views.garmin_details_view,name='garmin_details_view'),
|
||||||
# re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/delete/$',login_required(
|
# re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/delete/$',login_required(
|
||||||
# views.workout_code_delete_view),name='workout_code_delete'),
|
# views.workout_code_delete_view),name='workout_code_delete'),
|
||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/smoothenpace/$',views.workout_smoothenpace_view,name='workout_smoothenpace_view'),
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/smoothenpace/$',views.workout_smoothenpace_view,name='workout_smoothenpace_view'),
|
||||||
@@ -532,6 +534,7 @@ urlpatterns = [
|
|||||||
re_path(r'^me/polarauthorize/$',views.rower_polar_authorize,name='rower_polar_authorize'),
|
re_path(r'^me/polarauthorize/$',views.rower_polar_authorize,name='rower_polar_authorize'),
|
||||||
re_path(r'^me/revokeapp/(?P<id>\d+)/$',views.rower_revokeapp_view,name='rower_revokeapp_view'),
|
re_path(r'^me/revokeapp/(?P<id>\d+)/$',views.rower_revokeapp_view,name='rower_revokeapp_view'),
|
||||||
re_path(r'^me/stravaauthorize/$',views.rower_strava_authorize,name='rower_strava_authorize'),
|
re_path(r'^me/stravaauthorize/$',views.rower_strava_authorize,name='rower_strava_authorize'),
|
||||||
|
re_path(r'^me/garminauthorize/$',views.rower_garmin_authorize,name='rower_garmin_authorize'),
|
||||||
re_path(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize,name='rower_sporttracks_authorize'),
|
re_path(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize,name='rower_sporttracks_authorize'),
|
||||||
re_path(r'^me/underarmourauthorize/$',views.rower_underarmour_authorize,name='rower_underarmour_authorize'),
|
re_path(r'^me/underarmourauthorize/$',views.rower_underarmour_authorize,name='rower_underarmour_authorize'),
|
||||||
re_path(r'^me/tpauthorize/$',views.rower_tp_authorize,name='rower_tp_authorize'),
|
re_path(r'^me/tpauthorize/$',views.rower_tp_authorize,name='rower_tp_authorize'),
|
||||||
|
|||||||
@@ -401,6 +401,14 @@ def rower_c2_authorize(request):
|
|||||||
url += "&scope="+scope
|
url += "&scope="+scope
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
# Garmin authorization
|
||||||
|
@login_required()
|
||||||
|
def rower_garmin_authorize(request):
|
||||||
|
authorization_url,token,secret = garmin_stuff.garmin_authorize()
|
||||||
|
request.session['garmin_owner_key'] = token
|
||||||
|
request.session['garmin_owner_secret'] = secret
|
||||||
|
return HttpResponseRedirect(authorization_url)
|
||||||
|
|
||||||
# Strava Authorization
|
# Strava Authorization
|
||||||
@login_required()
|
@login_required()
|
||||||
def rower_strava_authorize(request):
|
def rower_strava_authorize(request):
|
||||||
@@ -705,7 +713,23 @@ def rower_process_polarcallback(request):
|
|||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
# process Garmin callback
|
||||||
|
@login_required()
|
||||||
|
def rower_process_garmincallback(request):
|
||||||
|
r = getrower(request.user)
|
||||||
|
absoluteurl = request.build_absolute_uri()
|
||||||
|
|
||||||
|
key = request.session['garmin_owner_key']
|
||||||
|
secret = request.session['garmin_owner_secret']
|
||||||
|
garmintoken,garminrefreshtoken = garmin_stuff.garmin_processcallback(absoluteurl,key,secret)
|
||||||
|
r.garmintoken = garmintoken
|
||||||
|
r.garminrefreshtoken = garminrefreshtoken
|
||||||
|
r.save()
|
||||||
|
|
||||||
|
successmessage = "Tokens stored. Good to go"
|
||||||
|
messages.info(request,successmessage)
|
||||||
|
url = reverse('workouts_view')
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
# Process Strava Callback
|
# Process Strava Callback
|
||||||
@login_required()
|
@login_required()
|
||||||
@@ -984,6 +1008,38 @@ def workout_stravaimport_view(request,message="",userid=0):
|
|||||||
|
|
||||||
return HttpResponse(res)
|
return HttpResponse(res)
|
||||||
|
|
||||||
|
# For push notifications from Garmin
|
||||||
|
@csrf_exempt
|
||||||
|
def garmin_summaries_view(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
raise Http404("Not allowed")
|
||||||
|
|
||||||
|
# POST request
|
||||||
|
data = json.loads(request.body)
|
||||||
|
activities = data['activities']
|
||||||
|
result = garmin_stuff.garmin_workouts_from_summaries(activities)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return HttpResponse(status=200)
|
||||||
|
|
||||||
|
return HttpResponse(status=400)
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def garmin_details_view(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
raise Http404("not allowed")
|
||||||
|
|
||||||
|
# POST request
|
||||||
|
data = json.loads(request.body)
|
||||||
|
result = garmin_stuff.garmin_workouts_from_details(data)
|
||||||
|
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return HttpResponse(status=200)
|
||||||
|
|
||||||
|
return HttpResponse(status=400)
|
||||||
|
|
||||||
|
|
||||||
# The page where you select which RunKeeper workout to import
|
# The page where you select which RunKeeper workout to import
|
||||||
@login_required()
|
@login_required()
|
||||||
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
|
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ from rowers.sporttracksstuff import sporttracks_open
|
|||||||
from rowers.tpstuff import tp_open
|
from rowers.tpstuff import tp_open
|
||||||
from iso8601 import ParseError
|
from iso8601 import ParseError
|
||||||
import rowers.stravastuff as stravastuff
|
import rowers.stravastuff as stravastuff
|
||||||
|
import rowers.garmin_stuff as garmin_stuff
|
||||||
from rowers.stravastuff import strava_open
|
from rowers.stravastuff import strava_open
|
||||||
import rowers.polarstuff as polarstuff
|
import rowers.polarstuff as polarstuff
|
||||||
import rowers.sporttracksstuff as sporttracksstuff
|
import rowers.sporttracksstuff as sporttracksstuff
|
||||||
|
|||||||
@@ -282,6 +282,10 @@ STRAVA_CLIENT_ID = CFG['strava_client_id']
|
|||||||
STRAVA_CLIENT_SECRET = CFG['strava_client_secret']
|
STRAVA_CLIENT_SECRET = CFG['strava_client_secret']
|
||||||
STRAVA_REDIRECT_URI = CFG['strava_callback']
|
STRAVA_REDIRECT_URI = CFG['strava_callback']
|
||||||
|
|
||||||
|
# Garmin
|
||||||
|
GARMIN_CLIENT_KEY = CFG["garmin_client_key"]
|
||||||
|
GARMIN_CLIENT_SECRET = CFG['garmin_client_secret']
|
||||||
|
GARMIN_REDIRECT_URI = CFG['garmin_callback']
|
||||||
|
|
||||||
# SportTracks
|
# SportTracks
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
|
|||||||
ALLOWED_HOSTS = ['localhost','127.0.0.1']
|
ALLOWED_HOSTS = ['localhost','127.0.0.1']
|
||||||
|
|
||||||
# INSTALLED_APPS += ['debug_toolbar',]
|
# INSTALLED_APPS += ['debug_toolbar',]
|
||||||
|
#INSTALLED_APPS += ["sslserver"]
|
||||||
|
|
||||||
# MIDDLEWARE_CLASSES += ['debug_toolbar.middleware.DebugToolbarMiddleware',]
|
# MIDDLEWARE_CLASSES += ['debug_toolbar.middleware.DebugToolbarMiddleware',]
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ urlpatterns += [
|
|||||||
# re_path(r'^admin/rq/',include('django_rq_dashboard.urls')),
|
# re_path(r'^admin/rq/',include('django_rq_dashboard.urls')),
|
||||||
re_path(r'^call\_back',rowersviews.rower_process_callback),
|
re_path(r'^call\_back',rowersviews.rower_process_callback),
|
||||||
re_path(r'^stravacall\_back',rowersviews.rower_process_stravacallback),
|
re_path(r'^stravacall\_back',rowersviews.rower_process_stravacallback),
|
||||||
|
re_path(r'^garmin\_callback',rowersviews.rower_process_garmincallback),
|
||||||
re_path(r'^sporttracks\_callback',rowersviews.rower_process_sporttrackscallback),
|
re_path(r'^sporttracks\_callback',rowersviews.rower_process_sporttrackscallback),
|
||||||
re_path(r'^underarmour\_callback',rowersviews.rower_process_underarmourcallback),
|
re_path(r'^underarmour\_callback',rowersviews.rower_process_underarmourcallback),
|
||||||
re_path(r'^polarflowcallback',rowersviews.rower_process_polarcallback),
|
re_path(r'^polarflowcallback',rowersviews.rower_process_polarcallback),
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Reference in New Issue
Block a user