Merge branch 'feature/underarmour' into develop
This commit is contained in:
Binary file not shown.
@@ -195,6 +195,10 @@ class Rower(models.Model):
|
|||||||
sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True)
|
sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True)
|
||||||
sporttracksrefreshtoken = models.CharField(default='',max_length=200,
|
sporttracksrefreshtoken = models.CharField(default='',max_length=200,
|
||||||
blank=True,null=True)
|
blank=True,null=True)
|
||||||
|
underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
||||||
|
underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True)
|
||||||
|
underarmourrefreshtoken = models.CharField(default='',max_length=200,
|
||||||
|
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)
|
||||||
|
|
||||||
runkeepertoken = models.CharField(default='',max_length=200,
|
runkeepertoken = models.CharField(default='',max_length=200,
|
||||||
@@ -360,6 +364,7 @@ class Workout(models.Model):
|
|||||||
maxhr = models.IntegerField(blank=True,null=True)
|
maxhr = models.IntegerField(blank=True,null=True)
|
||||||
uploadedtostrava = models.IntegerField(default=0)
|
uploadedtostrava = models.IntegerField(default=0)
|
||||||
uploadedtosporttracks = models.IntegerField(default=0)
|
uploadedtosporttracks = models.IntegerField(default=0)
|
||||||
|
uploadedtounderarmour = models.IntegerField(default=0)
|
||||||
uploadedtorunkeeper = models.IntegerField(default=0)
|
uploadedtorunkeeper = models.IntegerField(default=0)
|
||||||
|
|
||||||
# empower stuff
|
# empower stuff
|
||||||
|
|||||||
@@ -111,6 +111,24 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if workout.uploadedtounderarmour == 0 %}
|
||||||
|
{% if user.rower.underarmourtoken == None or user.rower.underarmourtoken == '' %}
|
||||||
|
<div class="grid_1">
|
||||||
|
<a href="/rowers/me/underarmourauthorize">
|
||||||
|
<img src="/static/img/uagray.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="grid_1">
|
||||||
|
<a href="/rowers/workout/{{ workout.id }}/underarmouruploadw"><img src="/static/img/uasquare.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="grid_1">
|
||||||
|
<a href="https://www.mapmyfitness.com/workout/{{ workout.uploadedtounderarmour }}">
|
||||||
|
<img src="/static/img/uachecked.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -138,8 +156,11 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid_6">
|
<div class="grid_6">
|
||||||
<div class="grid_2 alpha suffix_4">
|
<div class="grid_2 alpha">
|
||||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
|
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with Runkeeper" width="120"></a></p>
|
||||||
|
</div>
|
||||||
|
<div class="grid_2">
|
||||||
|
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,12 +43,22 @@
|
|||||||
<p>Import workouts from RunKeeper</p>
|
<p>Import workouts from RunKeeper</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid_6">
|
||||||
|
<div class="grid_3 alpha">
|
||||||
|
<p>
|
||||||
|
<a href="/rowers/workout/underarmourimport"><img src="/static/img/UAbtn.png" alt="Under Armour logo" width="140"></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid_3 omega">
|
||||||
|
<p>Import workouts from MapMyFitness/UnderArmour</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid_6 omega">
|
<div class="grid_6 omega">
|
||||||
<h3>Connect</h3>
|
<h3>Connect</h3>
|
||||||
|
|
||||||
<div class="grid_6">
|
<div class="grid_6 alpha">
|
||||||
<p>Click one of the below logos to connect to the service of your choice.
|
<p>Click one of the below logos to connect to the service of your choice.
|
||||||
You only need to do this once. After that, the site will have access until you
|
You only need to do this once. After that, the site will have access until you
|
||||||
revoke the authorization for the "rowingdata" app.</p>
|
revoke the authorization for the "rowingdata" app.</p>
|
||||||
@@ -67,10 +77,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid_6">
|
<div class="grid_6 alpha">
|
||||||
<div class="grid_2 alpha suffix_4">
|
<div class="grid_2 alpha">
|
||||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
|
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid_2 suffix_2 omega">
|
||||||
|
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Workouts{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Available on MapMyFitness (UnderArmour)</h1>
|
||||||
|
{% if workouts %}
|
||||||
|
<table width="70%" class="listtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Import </th>
|
||||||
|
<th> Date/Time </th>
|
||||||
|
<th> Duration </th>
|
||||||
|
<th> Total Distance</th>
|
||||||
|
<th> Type</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for workout in workouts %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/rowers/workout/underarmourimport/{{ workout|ualookup:'id' }}/">Import</a></td>
|
||||||
|
<td>{{ workout|ualookup:'starttime' }}</td>
|
||||||
|
<td>{{ workout|ualookup:'duration' }} </td>
|
||||||
|
<td>{{ workout|ualookup:'distance' }} m</td>
|
||||||
|
<td>{{ workout|ualookup:'type' }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p> No workouts found. We only list workouts with time data series. </p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from django import template
|
from django import template
|
||||||
from time import strftime
|
from time import strftime
|
||||||
|
import dateutil.parser
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@@ -27,6 +28,17 @@ def strfdeltah(tdelta):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def secondstotimestring(tdelta):
|
||||||
|
hours, rest = divmod(tdelta,3600)
|
||||||
|
minutes,seconds = divmod(rest,60)
|
||||||
|
res = "{hours:0>2}:{minutes:0>2}:{seconds:0>2}".format(
|
||||||
|
hours=hours,
|
||||||
|
minutes=minutes,
|
||||||
|
seconds=seconds,
|
||||||
|
)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def durationprint(d,dstring):
|
def durationprint(d,dstring):
|
||||||
if (d == None):
|
if (d == None):
|
||||||
@@ -57,6 +69,22 @@ def lookup(dict, key):
|
|||||||
s = s[:22]
|
s = s[:22]
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def ualookup(dict, key):
|
||||||
|
s = dict.get(key)
|
||||||
|
|
||||||
|
if key=='distance':
|
||||||
|
s = int(float(s))
|
||||||
|
|
||||||
|
if key=='duration':
|
||||||
|
s = secondstotimestring(int(s))
|
||||||
|
|
||||||
|
|
||||||
|
if key=='starttime':
|
||||||
|
s = dateutil.parser.parse(s)
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
@register.filter(name='times')
|
@register.filter(name='times')
|
||||||
def times(number):
|
def times(number):
|
||||||
return range(number)
|
return range(number)
|
||||||
|
|||||||
@@ -0,0 +1,370 @@
|
|||||||
|
# All the functionality needed to connect to Runkeeper
|
||||||
|
|
||||||
|
# Python
|
||||||
|
import oauth2 as oauth
|
||||||
|
import cgi
|
||||||
|
import requests
|
||||||
|
import requests.auth
|
||||||
|
import json
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import datetime
|
||||||
|
import numpy as np
|
||||||
|
from dateutil import parser
|
||||||
|
import time
|
||||||
|
import math
|
||||||
|
from math import sin,cos,atan2,sqrt
|
||||||
|
import os,sys
|
||||||
|
|
||||||
|
# Django
|
||||||
|
from django.shortcuts import render_to_response
|
||||||
|
from django.http import HttpResponseRedirect, HttpResponse,JsonResponse
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
# Project
|
||||||
|
# from .models import Profile
|
||||||
|
from rowingdata import rowingdata
|
||||||
|
import pandas as pd
|
||||||
|
from rowers.models import Rower,Workout
|
||||||
|
|
||||||
|
from rowsandall_app.settings import (
|
||||||
|
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||||
|
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||||
|
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_CLIENT_SECRET,
|
||||||
|
UNDERARMOUR_REDIRECT_URI,UNDERARMOUR_CLIENT_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Custom error class - to raise a NoTokenError
|
||||||
|
class UnderArmourNoTokenError(Exception):
|
||||||
|
def __init__(self,value):
|
||||||
|
self.value=value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self.value)
|
||||||
|
|
||||||
|
# Exponentially weighted moving average
|
||||||
|
# Used for data smoothing of the jagged data obtained by Strava
|
||||||
|
# See bitbucket issue 72
|
||||||
|
def ewmovingaverage(interval,window_size):
|
||||||
|
# Experimental code using Exponential Weighted moving average
|
||||||
|
|
||||||
|
try:
|
||||||
|
intervaldf = pd.DataFrame({'v':interval})
|
||||||
|
idf_ewma1 = intervaldf.ewm(span=window_size)
|
||||||
|
idf_ewma2 = intervaldf[::-1].ewm(span=window_size)
|
||||||
|
|
||||||
|
i_ewma1 = idf_ewma1.mean().ix[:,'v']
|
||||||
|
i_ewma2 = idf_ewma2.mean().ix[:,'v']
|
||||||
|
|
||||||
|
interval2 = np.vstack((i_ewma1,i_ewma2[::-1]))
|
||||||
|
interval2 = np.mean( interval2, axis=0) # average
|
||||||
|
except ValueError:
|
||||||
|
interval2 = interval
|
||||||
|
|
||||||
|
return interval2
|
||||||
|
|
||||||
|
from utils import geo_distance
|
||||||
|
|
||||||
|
|
||||||
|
# Custom exception handler, returns a 401 HTTP message
|
||||||
|
# with exception details in the json data
|
||||||
|
def custom_exception_handler(exc,message):
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": str(exc),
|
||||||
|
"detail": message,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
res = HttpResponse(message)
|
||||||
|
res.status_code = 401
|
||||||
|
res.json = json.dumps(response)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
# Refresh ST token using refresh token
|
||||||
|
def do_refresh_token(refreshtoken,access_token):
|
||||||
|
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET)
|
||||||
|
post_data = {"grant_type": "refresh_token",
|
||||||
|
"client_secret": UNDERARMOUR_CLIENT_SECRET,
|
||||||
|
"client_id":UNDERARMOUR_CLIENT_KEY,
|
||||||
|
"refresh_token": refreshtoken,
|
||||||
|
}
|
||||||
|
headers = {'user-agent': 'sanderroosendaal',
|
||||||
|
"Api-Key":UNDERARMOUR_CLIENT_KEY,
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'authorization': 'Bearer %s' % access_token}
|
||||||
|
|
||||||
|
url = "https://api.ua.com/v7.1/oauth2/access_token/"
|
||||||
|
|
||||||
|
response = requests.post(url,
|
||||||
|
data=post_data,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
token_json = response.json()
|
||||||
|
thetoken = token_json['access_token']
|
||||||
|
expires_in = token_json['expires_in']
|
||||||
|
try:
|
||||||
|
refresh_token = token_json['refresh_token']
|
||||||
|
except KeyError:
|
||||||
|
refresh_token = refreshtoken
|
||||||
|
|
||||||
|
return [thetoken,expires_in,refresh_token]
|
||||||
|
|
||||||
|
# Exchange access code for long-lived access token
|
||||||
|
def get_token(code):
|
||||||
|
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET)
|
||||||
|
post_data = {
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": code,
|
||||||
|
"client_secret": UNDERARMOUR_CLIENT_SECRET,
|
||||||
|
"client_id":UNDERARMOUR_CLIENT_KEY,
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'user-agent': 'sanderroosendaal',
|
||||||
|
"Api-Key":UNDERARMOUR_CLIENT_KEY,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post("https://api.ua.com/v7.1/oauth2/access_token/",
|
||||||
|
data=post_data,
|
||||||
|
headers=headers)
|
||||||
|
try:
|
||||||
|
token_json = response.json()
|
||||||
|
thetoken = token_json['access_token']
|
||||||
|
expires_in = token_json['expires_in']
|
||||||
|
refresh_token = token_json['refresh_token']
|
||||||
|
except KeyError:
|
||||||
|
thetoken = 0
|
||||||
|
|
||||||
|
return thetoken,expires_in,refresh_token
|
||||||
|
|
||||||
|
# Make authorization URL including random string
|
||||||
|
def make_authorization_url(request):
|
||||||
|
# Generate a random string for the state parameter
|
||||||
|
# Save it for use later to prevent xsrf attacks
|
||||||
|
from uuid import uuid4
|
||||||
|
state = str(uuid4())
|
||||||
|
|
||||||
|
params = {"client_id": UNDERARMOUR_CLIENT_KEY,
|
||||||
|
"response_type": "code",
|
||||||
|
"redirect_uri": UNDERARMOUR_REDIRECT_URI,
|
||||||
|
}
|
||||||
|
import urllib
|
||||||
|
url = "https://www.mapmyfitness.com/v7.1/oauth2/uacf/authorize/" +urllib.urlencode(params)
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
# Get list of workouts available on Underarmour
|
||||||
|
def get_underarmour_workout_list(user):
|
||||||
|
r = Rower.objects.get(user=user)
|
||||||
|
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||||
|
s = "Token doesn't exist. Need to authorize"
|
||||||
|
return custom_exception_handler(401,s)
|
||||||
|
else:
|
||||||
|
# ready to fetch. Hurray
|
||||||
|
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||||
|
headers = {'Authorization': authorizationstring,
|
||||||
|
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||||
|
'user-agent': 'sanderroosendaal',
|
||||||
|
'Content-Type': 'application/json'}
|
||||||
|
url = "https://api.ua.com/v7.1/workout/?user="+str(get_userid(r.underarmourtoken))
|
||||||
|
|
||||||
|
s = requests.get(url,headers=headers)
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Get workout summary data by Underarmour ID
|
||||||
|
def get_underarmour_workout(user,underarmourid):
|
||||||
|
r = Rower.objects.get(user=user)
|
||||||
|
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||||
|
return custom_exception_handler(401,s)
|
||||||
|
s = "Token doesn't exist. Need to authorize"
|
||||||
|
else:
|
||||||
|
# ready to fetch. Hurray
|
||||||
|
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||||
|
headers = {'Authorization': authorizationstring,
|
||||||
|
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||||
|
'user-agent': 'sanderroosendaal',
|
||||||
|
'Content-Type': 'application/json'}
|
||||||
|
url = "https://api.ua.com/v7.1/workout/"+str(underarmourid)+"/?field_set=time_series"
|
||||||
|
s = requests.get(url,headers=headers)
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Create Workout Data for upload to Underarmour
|
||||||
|
def createunderarmourworkoutdata(w):
|
||||||
|
filename = w.csvfilename
|
||||||
|
try:
|
||||||
|
row = rowingdata(filename)
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
averagehr = int(row.df[' HRCur (bpm)'].mean())
|
||||||
|
maxhr = int(row.df[' HRCur (bpm)'].max())
|
||||||
|
duration = w.duration.hour*3600
|
||||||
|
duration += w.duration.minute*60
|
||||||
|
duration += w.duration.second
|
||||||
|
duration += +1.0e-6*w.duration.microsecond
|
||||||
|
name = w.name
|
||||||
|
notes = w.notes
|
||||||
|
|
||||||
|
# adding diff, trying to see if this is valid
|
||||||
|
#t = row.df.ix[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)']
|
||||||
|
t = row.df.ix[:,'TimeStamp (sec)'].values-row.df.ix[0,'TimeStamp (sec)']
|
||||||
|
t[0] = t[1]
|
||||||
|
|
||||||
|
d = row.df.ix[:,'cum_dist'].values
|
||||||
|
d[0] = d[1]
|
||||||
|
t = t.astype(int)
|
||||||
|
d = d.astype(int)
|
||||||
|
spm = row.df[' Cadence (stokes/min)'].astype(int)
|
||||||
|
spm[0] = spm[1]
|
||||||
|
hr = row.df[' HRCur (bpm)'].astype(int)
|
||||||
|
|
||||||
|
haslatlon=1
|
||||||
|
|
||||||
|
try:
|
||||||
|
lat = row.df[' latitude'].values
|
||||||
|
lon = row.df[' longitude'].values
|
||||||
|
if not lat.std() and not lon.std():
|
||||||
|
haslatlon = 0
|
||||||
|
except KeyError:
|
||||||
|
haslatlon = 0
|
||||||
|
|
||||||
|
# short for debugging
|
||||||
|
t = t[:10]
|
||||||
|
d = d[:10]
|
||||||
|
lat = lat[:10]
|
||||||
|
hr = hr[:10]
|
||||||
|
lon = lon[:10]
|
||||||
|
spm = spm[:10]
|
||||||
|
|
||||||
|
# path data
|
||||||
|
if haslatlon:
|
||||||
|
locdata = []
|
||||||
|
for e in zip(t,lat,lon):
|
||||||
|
point = {
|
||||||
|
'lat':e[1],
|
||||||
|
'lng':e[2],
|
||||||
|
'elevation':0,
|
||||||
|
}
|
||||||
|
locdata.append([e[0],point])
|
||||||
|
|
||||||
|
hrdata = []
|
||||||
|
for e in zip(t,hr):
|
||||||
|
point = [e[0],
|
||||||
|
e[1]
|
||||||
|
]
|
||||||
|
hrdata.append(point)
|
||||||
|
|
||||||
|
distancedata = []
|
||||||
|
for e in zip(t,d):
|
||||||
|
point = [e[0],
|
||||||
|
e[1]
|
||||||
|
]
|
||||||
|
distancedata.append(point)
|
||||||
|
|
||||||
|
spmdata = []
|
||||||
|
for e in zip(t,spm):
|
||||||
|
spmdata.append([e[0],e[1]])
|
||||||
|
|
||||||
|
start_time = w.startdatetime.isoformat()
|
||||||
|
|
||||||
|
timeseries = {
|
||||||
|
"distance": distancedata,
|
||||||
|
"heartrate": hrdata,
|
||||||
|
"cadence": spmdata,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregrates = {
|
||||||
|
"elapsed_time_total": int(duration),
|
||||||
|
"distance_total": int(max(d)),
|
||||||
|
"heartrate_avg": averagehr,
|
||||||
|
"heart_rate_min": int(min(hr)),
|
||||||
|
"heart_rate_max": int(max(hr)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# if haslatlon:
|
||||||
|
# timeseries["position"] = locdata
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"name": name,
|
||||||
|
"start_datetime": start_time,
|
||||||
|
"time_series": timeseries,
|
||||||
|
"start_locale_timezone": "Etc/UTC",
|
||||||
|
"activity_type": "/v7.1/activity_type/128/",
|
||||||
|
"notes": notes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Obtain Underarmour Workout ID and activity type
|
||||||
|
def get_idfromuri(user,links):
|
||||||
|
id = links['self'][0]['id']
|
||||||
|
typeid = links['activity_type'][0]['id']
|
||||||
|
|
||||||
|
|
||||||
|
typename = get_typefromid(typeid,user)
|
||||||
|
|
||||||
|
return id,typename
|
||||||
|
|
||||||
|
def getidfromresponse(response):
|
||||||
|
t = json.loads(response.text)
|
||||||
|
|
||||||
|
links = t["_links"]
|
||||||
|
|
||||||
|
id = links["self"][0]["id"]
|
||||||
|
|
||||||
|
return int(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_typefromid(typeid,user):
|
||||||
|
r = Rower.objects.get(user=user)
|
||||||
|
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||||
|
headers = {'Authorization': authorizationstring,
|
||||||
|
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||||
|
'user-agent': 'sanderroosendaal',
|
||||||
|
'Content-Type': 'application/json'}
|
||||||
|
import urllib
|
||||||
|
url = "https://api.ua.com/v7.1/activity_type/"+str(typeid)
|
||||||
|
response = requests.get(url,headers=headers)
|
||||||
|
|
||||||
|
me_json = response.json()
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = me_json['name']
|
||||||
|
except KeyError:
|
||||||
|
res = 0
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Get user id, having access token
|
||||||
|
# Handy for checking if the API access is working
|
||||||
|
def get_userid(access_token):
|
||||||
|
authorizationstring = str('Bearer ' + access_token)
|
||||||
|
headers = {'Authorization': authorizationstring,
|
||||||
|
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||||
|
'user-agent': 'sanderroosendaal',
|
||||||
|
'Content-Type': 'application/json'}
|
||||||
|
import urllib
|
||||||
|
url = "https://api.ua.com/v7.1/user/self"
|
||||||
|
response = requests.get(url,headers=headers)
|
||||||
|
|
||||||
|
me_json = response.json()
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = me_json['id']
|
||||||
|
except KeyError:
|
||||||
|
res = 0
|
||||||
|
|
||||||
|
return res
|
||||||
@@ -229,12 +229,15 @@ urlpatterns = [
|
|||||||
url(r'^workout/sporttracksimport/(\d+)/$',views.workout_getsporttracksworkout_view),
|
url(r'^workout/sporttracksimport/(\d+)/$',views.workout_getsporttracksworkout_view),
|
||||||
url(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view),
|
url(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view),
|
||||||
url(r'^workout/runkeeperimport/(\d+)/$',views.workout_getrunkeeperworkout_view),
|
url(r'^workout/runkeeperimport/(\d+)/$',views.workout_getrunkeeperworkout_view),
|
||||||
|
url(r'^workout/underarmourimport/$',views.workout_underarmourimport_view),
|
||||||
|
url(r'^workout/underarmourimport/(\d+)/$',views.workout_getunderarmourworkout_view),
|
||||||
url(r'^workout/(\d+)/deleteconfirm$',views.workout_delete_confirm_view),
|
url(r'^workout/(\d+)/deleteconfirm$',views.workout_delete_confirm_view),
|
||||||
url(r'^workout/(\d+)/c2uploadw/$',views.workout_c2_upload_view),
|
url(r'^workout/(\d+)/c2uploadw/$',views.workout_c2_upload_view),
|
||||||
url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view),
|
url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view),
|
||||||
url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view),
|
url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view),
|
||||||
url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_view),
|
url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_view),
|
||||||
url(r'^workout/(\d+)/runkeeperuploadw/$',views.workout_runkeeper_upload_view),
|
url(r'^workout/(\d+)/runkeeperuploadw/$',views.workout_runkeeper_upload_view),
|
||||||
|
url(r'^workout/(\d+)/underarmouruploadw/$',views.workout_underarmour_upload_view),
|
||||||
url(r'^multi-compare$',views.multi_compare_view),
|
url(r'^multi-compare$',views.multi_compare_view),
|
||||||
url(r'^me/teams/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
|
url(r'^me/teams/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
|
||||||
url(r'^me/teams/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
|
url(r'^me/teams/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
|
||||||
@@ -269,8 +272,10 @@ urlpatterns = [
|
|||||||
url(r'^me/revokeapp/(\d+)$',views.rower_revokeapp_view),
|
url(r'^me/revokeapp/(\d+)$',views.rower_revokeapp_view),
|
||||||
url(r'^me/stravaauthorize/$',views.rower_strava_authorize),
|
url(r'^me/stravaauthorize/$',views.rower_strava_authorize),
|
||||||
url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize),
|
url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize),
|
||||||
|
url(r'^me/underarmourauthorize/$',views.rower_underarmour_authorize),
|
||||||
url(r'^me/runkeeperauthorize/$',views.rower_runkeeper_authorize),
|
url(r'^me/runkeeperauthorize/$',views.rower_runkeeper_authorize),
|
||||||
url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh),
|
url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh),
|
||||||
|
url(r'^me/underarmourrefresh/$',views.rower_underarmour_token_refresh),
|
||||||
url(r'^me/c2refresh/$',views.rower_c2_token_refresh),
|
url(r'^me/c2refresh/$',views.rower_c2_token_refresh),
|
||||||
url(r'^me/favoritecharts/$',views.rower_favoritecharts_view),
|
url(r'^me/favoritecharts/$',views.rower_favoritecharts_view),
|
||||||
url(r'^email/send/$', views.sendmail),
|
url(r'^email/send/$', views.sendmail),
|
||||||
|
|||||||
+393
-1
@@ -56,6 +56,7 @@ from sporttracksstuff import SportTracksNoTokenError
|
|||||||
from iso8601 import ParseError
|
from iso8601 import ParseError
|
||||||
import stravastuff
|
import stravastuff
|
||||||
import sporttracksstuff
|
import sporttracksstuff
|
||||||
|
import underarmourstuff
|
||||||
import runkeeperstuff
|
import runkeeperstuff
|
||||||
import ownapistuff
|
import ownapistuff
|
||||||
from ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
|
from ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
|
||||||
@@ -64,6 +65,8 @@ from rowsandall_app.settings import (
|
|||||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||||
SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI,
|
SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI,
|
||||||
SPORTTRACKS_CLIENT_SECRET,
|
SPORTTRACKS_CLIENT_SECRET,
|
||||||
|
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_REDIRECT_URI,
|
||||||
|
UNDERARMOUR_CLIENT_SECRET,UNDERARMOUR_CLIENT_KEY,
|
||||||
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
|
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -211,6 +214,15 @@ def get_time(second):
|
|||||||
def getidfromsturi(uri,length=8):
|
def getidfromsturi(uri,length=8):
|
||||||
return uri[len(uri)-length:]
|
return uri[len(uri)-length:]
|
||||||
|
|
||||||
|
def splituadata(lijst):
|
||||||
|
t = []
|
||||||
|
y = []
|
||||||
|
for d in lijst:
|
||||||
|
t.append(d[0])
|
||||||
|
y.append(d[1])
|
||||||
|
|
||||||
|
return np.array(t),np.array(y)
|
||||||
|
|
||||||
def splitrunkeeperlatlongdata(lijst,tname,latname,lonname):
|
def splitrunkeeperlatlongdata(lijst,tname,latname,lonname):
|
||||||
t = []
|
t = []
|
||||||
lat = []
|
lat = []
|
||||||
@@ -813,6 +825,173 @@ def add_workout_from_stdata(user,importid,data):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
unixtime = cum_time+starttimeunix
|
||||||
|
unixtime[0] = starttimeunix
|
||||||
|
|
||||||
|
df['TimeStamp (sec)'] = unixtime
|
||||||
|
|
||||||
|
|
||||||
|
dt = np.diff(cum_time).mean()
|
||||||
|
wsize = round(5./dt)
|
||||||
|
|
||||||
|
velo2 = stravastuff.ewmovingaverage(velo,wsize)
|
||||||
|
|
||||||
|
df[' Stroke500mPace (sec/500m)'] = 500./velo2
|
||||||
|
|
||||||
|
|
||||||
|
df = df.fillna(0)
|
||||||
|
|
||||||
|
df.sort_values(by='TimeStamp (sec)',ascending=True)
|
||||||
|
|
||||||
|
timestr = strftime("%Y%m%d-%H%M%S")
|
||||||
|
|
||||||
|
csvfilename ='media/Import_'+str(importid)+'.csv'
|
||||||
|
|
||||||
|
res = df.to_csv(csvfilename+'.gz',index_label='index',
|
||||||
|
compression='gzip')
|
||||||
|
|
||||||
|
id,message = dataprep.save_workout_database(csvfilename,r,
|
||||||
|
workouttype=workouttype,
|
||||||
|
title=title,
|
||||||
|
notes=comments)
|
||||||
|
|
||||||
|
return (id,message)
|
||||||
|
|
||||||
|
# Create workout from SportTracks Data, which are slightly different
|
||||||
|
# than Strava or Concept2 data
|
||||||
|
def add_workout_from_underarmourdata(user,importid,data):
|
||||||
|
workouttype = 'water'
|
||||||
|
|
||||||
|
try:
|
||||||
|
comments = data['notes']
|
||||||
|
except:
|
||||||
|
comments = ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
thetimezone = tz(data['start_locale_timezone'])
|
||||||
|
except:
|
||||||
|
thetimezone = 'UTC'
|
||||||
|
|
||||||
|
r = Rower.objects.get(user=user)
|
||||||
|
try:
|
||||||
|
rowdatetime = iso8601.parse_date(data['start_datetime'])
|
||||||
|
except iso8601.ParseError:
|
||||||
|
try:
|
||||||
|
rowdatetime = datetime.datetime.strptime(data['start_datetime'],"%Y-%m-%d %H:%M:%S")
|
||||||
|
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
rowdatetime = dateutil.parser.parse(data['start_datetime'])
|
||||||
|
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
|
||||||
|
except:
|
||||||
|
rowdatetime = datetime.datetime.strptime(data['date'],"%Y-%m-%d %H:%M:%S")
|
||||||
|
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
|
||||||
|
starttimeunix = mktime(rowdatetime.utctimetuple())
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
title = data['name']
|
||||||
|
except:
|
||||||
|
title = "Imported data"
|
||||||
|
|
||||||
|
timeseries = data['time_series']
|
||||||
|
|
||||||
|
# position, distance, speed, cadence, power,
|
||||||
|
|
||||||
|
res = splituadata(timeseries['distance'])
|
||||||
|
|
||||||
|
distance = res[1]
|
||||||
|
|
||||||
|
times_distance = res[0]
|
||||||
|
|
||||||
|
print distance[0:5]
|
||||||
|
print times_distance[0:5]
|
||||||
|
|
||||||
|
try:
|
||||||
|
l = timeseries['position']
|
||||||
|
|
||||||
|
res = splituadata(l)
|
||||||
|
times_location = res[0]
|
||||||
|
latlong = res[1]
|
||||||
|
latcoord = []
|
||||||
|
loncoord = []
|
||||||
|
|
||||||
|
for coord in latlong:
|
||||||
|
lat = coord['lat']
|
||||||
|
lon = coord['lng']
|
||||||
|
latcoord.append(lat)
|
||||||
|
loncoord.append(lon)
|
||||||
|
except:
|
||||||
|
times_location = times_distance
|
||||||
|
latcoord = np.zeros(len(times_distance))
|
||||||
|
loncoord = np.zeros(len(times_distance))
|
||||||
|
if workouttype == 'water':
|
||||||
|
workouttype = 'rower'
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = splituadata(timeseries['cadence'])
|
||||||
|
times_spm = res[0]
|
||||||
|
spm = res[1]
|
||||||
|
except KeyError:
|
||||||
|
times_spm = times_distance
|
||||||
|
spm = 0*times_distance
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = splituadata(timeseries['heartrate'])
|
||||||
|
hr = res[1]
|
||||||
|
times_hr = res[0]
|
||||||
|
except KeyError:
|
||||||
|
times_hr = times_distance
|
||||||
|
hr = 0*times_distance
|
||||||
|
|
||||||
|
|
||||||
|
# create data series and remove duplicates
|
||||||
|
distseries = pd.Series(distance,index=times_distance)
|
||||||
|
distseries = distseries.groupby(distseries.index).first()
|
||||||
|
latseries = pd.Series(latcoord,index=times_location)
|
||||||
|
latseries = latseries.groupby(latseries.index).first()
|
||||||
|
lonseries = pd.Series(loncoord,index=times_location)
|
||||||
|
lonseries = lonseries.groupby(lonseries.index).first()
|
||||||
|
spmseries = pd.Series(spm,index=times_spm)
|
||||||
|
spmseries = spmseries.groupby(spmseries.index).first()
|
||||||
|
hrseries = pd.Series(hr,index=times_hr)
|
||||||
|
hrseries = hrseries.groupby(hrseries.index).first()
|
||||||
|
|
||||||
|
|
||||||
|
# Create dicts and big dataframe
|
||||||
|
d = {
|
||||||
|
' Horizontal (meters)': distseries,
|
||||||
|
' latitude': latseries,
|
||||||
|
' longitude': lonseries,
|
||||||
|
' Cadence (stokes/min)': spmseries,
|
||||||
|
' HRCur (bpm)' : hrseries,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
df = pd.DataFrame(d)
|
||||||
|
|
||||||
|
df = df.groupby(level=0).last()
|
||||||
|
|
||||||
|
cum_time = df.index.values
|
||||||
|
df[' ElapsedTime (sec)'] = cum_time
|
||||||
|
|
||||||
|
velo = df[' Horizontal (meters)'].diff()/df[' ElapsedTime (sec)'].diff()
|
||||||
|
|
||||||
|
df[' Power (watts)'] = 0.0*velo
|
||||||
|
|
||||||
|
nr_rows = len(velo.values)
|
||||||
|
|
||||||
|
df[' DriveLength (meters)'] = np.zeros(nr_rows)
|
||||||
|
df[' StrokeDistance (meters)'] = np.zeros(nr_rows)
|
||||||
|
df[' DriveTime (ms)'] = np.zeros(nr_rows)
|
||||||
|
df[' StrokeRecoveryTime (ms)'] = np.zeros(nr_rows)
|
||||||
|
df[' AverageDriveForce (lbs)'] = np.zeros(nr_rows)
|
||||||
|
df[' PeakDriveForce (lbs)'] = np.zeros(nr_rows)
|
||||||
|
df[' lapIdx'] = np.zeros(nr_rows)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
unixtime = cum_time+starttimeunix
|
unixtime = cum_time+starttimeunix
|
||||||
unixtime[0] = starttimeunix
|
unixtime[0] = starttimeunix
|
||||||
|
|
||||||
@@ -878,6 +1057,20 @@ def sporttracks_open(user):
|
|||||||
|
|
||||||
return thetoken
|
return thetoken
|
||||||
|
|
||||||
|
# Checks if user has UnderArmour token, renews them if they are expired
|
||||||
|
def underarmour_open(user):
|
||||||
|
r = Rower.objects.get(user=user)
|
||||||
|
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||||
|
s = "Token doesn't exist. Need to authorize"
|
||||||
|
raise UnderarmourNoTokenError("User has no token")
|
||||||
|
else:
|
||||||
|
if (timezone.now()>r.underarmourtokenexpirydate):
|
||||||
|
thetoken = underarmourstuff.rower_underarmour_token_refresh(user)
|
||||||
|
else:
|
||||||
|
thetoken = r.underarmourtoken
|
||||||
|
|
||||||
|
return thetoken
|
||||||
|
|
||||||
# Checks if user has SportTracks token, renews them if they are expired
|
# Checks if user has SportTracks token, renews them if they are expired
|
||||||
def runkeeper_open(user):
|
def runkeeper_open(user):
|
||||||
r = Rower.objects.get(user=user)
|
r = Rower.objects.get(user=user)
|
||||||
@@ -1237,6 +1430,72 @@ def workout_runkeeper_upload_view(request,id=0):
|
|||||||
|
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
# Upload workout to Underarmour
|
||||||
|
@login_required()
|
||||||
|
def workout_underarmour_upload_view(request,id=0):
|
||||||
|
message = ""
|
||||||
|
try:
|
||||||
|
w = Workout.objects.get(id=id)
|
||||||
|
r = w.user
|
||||||
|
except Workout.DoesNotExist:
|
||||||
|
raise Http404("Workout doesn't exist")
|
||||||
|
|
||||||
|
try:
|
||||||
|
thetoken = underarmour_open(r.user)
|
||||||
|
except UnderarmourNoTokenError:
|
||||||
|
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
|
||||||
|
|
||||||
|
# ready to upload. Hurray
|
||||||
|
|
||||||
|
if (checkworkoutuser(request.user,w)):
|
||||||
|
data = underarmourstuff.createunderarmourworkoutdata(w)
|
||||||
|
# return HttpResponse(json.dumps(data))
|
||||||
|
if not data:
|
||||||
|
message = "Data error"
|
||||||
|
url = reverse(workout_export_view,
|
||||||
|
kwargs = {
|
||||||
|
'message':str(message),
|
||||||
|
'id':str(w.id),
|
||||||
|
})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
authorizationstring = str('Bearer ' + thetoken)
|
||||||
|
headers = {'Authorization': authorizationstring,
|
||||||
|
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||||
|
'user-agent': 'sanderroosendaal',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
import urllib
|
||||||
|
url = "https://api.ua.com/v7.1/workout/"
|
||||||
|
response = requests.post(url,headers=headers,data=json.dumps(data))
|
||||||
|
|
||||||
|
# check for duplicate error first
|
||||||
|
if (response.status_code == 409 ):
|
||||||
|
message = "Duplicate error"
|
||||||
|
w.uploadedtounderarmour = -1
|
||||||
|
w.save()
|
||||||
|
elif (response.status_code == 201 or response.status_code==200):
|
||||||
|
underarmourid = underarmourstuff.getidfromresponse(response)
|
||||||
|
w.uploadedtounderarmour = underarmourid
|
||||||
|
w.save()
|
||||||
|
url = "/rowers/workout/"+str(w.id)+"/export"
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
s = response
|
||||||
|
message = "Something went wrong in workout_underarmour_upload_view: %s - %s" % (s.reason,s.text)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = "You are not authorized to upload this workout"
|
||||||
|
|
||||||
|
url = reverse(workout_export_view,
|
||||||
|
kwargs = {
|
||||||
|
'message':str(message),
|
||||||
|
'id':str(w.id),
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
# Upload workout to SportTracks
|
# Upload workout to SportTracks
|
||||||
@login_required()
|
@login_required()
|
||||||
def workout_sporttracks_upload_view(request,id=0):
|
def workout_sporttracks_upload_view(request,id=0):
|
||||||
@@ -1373,6 +1632,24 @@ def rower_sporttracks_authorize(request):
|
|||||||
|
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
# Underarmour Authorization
|
||||||
|
@login_required()
|
||||||
|
def rower_underarmour_authorize(request):
|
||||||
|
# Generate a random string for the state parameter
|
||||||
|
# Save it for use later to prevent xsrf attacks
|
||||||
|
from uuid import uuid4
|
||||||
|
state = str(uuid4())
|
||||||
|
|
||||||
|
redirect_uri = UNDERARMOUR_REDIRECT_URI
|
||||||
|
redirect_uri = 'http://localhost:8000/underarmour_callback'
|
||||||
|
|
||||||
|
url = 'https://www.mapmyfitness.com/v7.1/oauth2/authorize/?' \
|
||||||
|
'client_id={0}&response_type=code&redirect_uri={1}'.format(
|
||||||
|
UNDERARMOUR_CLIENT_KEY, redirect_uri
|
||||||
|
)
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
# Concept2 token refresh. URL for manual refresh. Not visible to users
|
# Concept2 token refresh. URL for manual refresh. Not visible to users
|
||||||
@login_required()
|
@login_required()
|
||||||
def rower_c2_token_refresh(request):
|
def rower_c2_token_refresh(request):
|
||||||
@@ -1398,11 +1675,37 @@ def rower_c2_token_refresh(request):
|
|||||||
|
|
||||||
return imports_view(request,successmessage=successmessage,message=message)
|
return imports_view(request,successmessage=successmessage,message=message)
|
||||||
|
|
||||||
|
# Underarmour token refresh. URL for manual refresh. Not visible to users
|
||||||
|
@login_required()
|
||||||
|
def rower_underarmour_token_refresh(request):
|
||||||
|
r = Rower.objects.get(user=request.user)
|
||||||
|
res = underarmourstuff.do_refresh_token(
|
||||||
|
r.underarmourrefreshtoken,
|
||||||
|
r.underarmourtoken
|
||||||
|
)
|
||||||
|
access_token = res[0]
|
||||||
|
expires_in = res[1]
|
||||||
|
refresh_token = res[2]
|
||||||
|
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
|
||||||
|
|
||||||
|
r = Rower.objects.get(user=request.user)
|
||||||
|
r.underarmourtoken = access_token
|
||||||
|
r.underarmourtokenexpirydate = expirydatetime
|
||||||
|
r.underarmourrefreshtoken = refresh_token
|
||||||
|
|
||||||
|
r.save()
|
||||||
|
|
||||||
|
successmessage = "Tokens refreshed. Good to go"
|
||||||
|
return imports_view(request,successmessage=successmessage)
|
||||||
|
|
||||||
|
|
||||||
# SportTracks token refresh. URL for manual refresh. Not visible to users
|
# SportTracks token refresh. URL for manual refresh. Not visible to users
|
||||||
@login_required()
|
@login_required()
|
||||||
def rower_sporttracks_token_refresh(request):
|
def rower_sporttracks_token_refresh(request):
|
||||||
r = Rower.objects.get(user=request.user)
|
r = Rower.objects.get(user=request.user)
|
||||||
res = sporttracksstuff.do_refresh_token(r.sporttracksrefreshtoken)
|
res = sporttracksstuff.do_refresh_token(
|
||||||
|
r.sporttracksrefreshtoken,
|
||||||
|
)
|
||||||
access_token = res[0]
|
access_token = res[0]
|
||||||
expires_in = res[1]
|
expires_in = res[1]
|
||||||
refresh_token = res[2]
|
refresh_token = res[2]
|
||||||
@@ -1535,6 +1838,28 @@ def rower_process_sporttrackscallback(request):
|
|||||||
successmessage = "Tokens stored. Good to go"
|
successmessage = "Tokens stored. Good to go"
|
||||||
return imports_view(request,successmessage=successmessage)
|
return imports_view(request,successmessage=successmessage)
|
||||||
|
|
||||||
|
# Process Underarmour callback
|
||||||
|
@login_required()
|
||||||
|
def rower_process_underarmourcallback(request):
|
||||||
|
code = request.GET['code']
|
||||||
|
res = underarmourstuff.get_token(code)
|
||||||
|
|
||||||
|
|
||||||
|
access_token = res[0]
|
||||||
|
expires_in = res[1]
|
||||||
|
refresh_token = res[2]
|
||||||
|
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
|
||||||
|
|
||||||
|
r = Rower.objects.get(user=request.user)
|
||||||
|
r.underarmourtoken = access_token
|
||||||
|
r.underarmourtokenexpirydate = expirydatetime
|
||||||
|
r.underarmourrefreshtoken = refresh_token
|
||||||
|
|
||||||
|
r.save()
|
||||||
|
|
||||||
|
successmessage = "Tokens stored. Good to go"
|
||||||
|
return imports_view(request,successmessage=successmessage)
|
||||||
|
|
||||||
# Process Own API callback - for API testing purposes
|
# Process Own API callback - for API testing purposes
|
||||||
@login_required()
|
@login_required()
|
||||||
def rower_process_testcallback(request):
|
def rower_process_testcallback(request):
|
||||||
@@ -4839,6 +5164,50 @@ def workout_runkeeperimport_view(request,message=""):
|
|||||||
|
|
||||||
return HttpResponse(res)
|
return HttpResponse(res)
|
||||||
|
|
||||||
|
# The page where you select which RunKeeper workout to import
|
||||||
|
@login_required()
|
||||||
|
def workout_underarmourimport_view(request,message=""):
|
||||||
|
res = underarmourstuff.get_underarmour_workout_list(request.user)
|
||||||
|
if (res.status_code != 200):
|
||||||
|
if (res.status_code == 401):
|
||||||
|
r = Rower.objects.get(user=request.user)
|
||||||
|
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||||
|
s = "Token doesn't exist. Need to authorize"
|
||||||
|
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
|
||||||
|
message = "Something went wrong in workout_underarmourimport_view"
|
||||||
|
if settings.DEBUG:
|
||||||
|
return HttpResponse(res)
|
||||||
|
else:
|
||||||
|
url = reverse(workouts_view,
|
||||||
|
kwargs = {
|
||||||
|
'message': str(message)
|
||||||
|
})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
workouts = []
|
||||||
|
items = res.json()['_embedded']['workouts']
|
||||||
|
for item in items:
|
||||||
|
if 'has_time_series' in item:
|
||||||
|
if item['has_time_series']:
|
||||||
|
s = item['start_datetime']
|
||||||
|
i,r = underarmourstuff.get_idfromuri(request.user,item['_links'])
|
||||||
|
n = item['name']
|
||||||
|
d = item['aggregates']['distance_total']
|
||||||
|
ttot = item['aggregates']['active_time_total']
|
||||||
|
keys = ['id','distance','duration','starttime','type']
|
||||||
|
values = [i,d,ttot,s,r]
|
||||||
|
thedict = dict(zip(keys,values))
|
||||||
|
|
||||||
|
workouts.append(thedict)
|
||||||
|
|
||||||
|
return render(request,'underarmour_list_import.html',
|
||||||
|
{'workouts':workouts,
|
||||||
|
'teams':get_my_teams(request.user),
|
||||||
|
'message':message,
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponse(res)
|
||||||
|
|
||||||
# The page where you select which SportTracks workout to import
|
# The page where you select which SportTracks workout to import
|
||||||
@login_required()
|
@login_required()
|
||||||
def workout_sporttracksimport_view(request,message=""):
|
def workout_sporttracksimport_view(request,message=""):
|
||||||
@@ -5021,6 +5390,29 @@ def workout_getrunkeeperworkout_view(request,runkeeperid):
|
|||||||
})
|
})
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
# Imports a workout from Underarmour
|
||||||
|
@login_required()
|
||||||
|
def workout_getunderarmourworkout_view(request,underarmourid):
|
||||||
|
res = underarmourstuff.get_underarmour_workout(request.user,underarmourid)
|
||||||
|
data = res.json()
|
||||||
|
|
||||||
|
id,message = add_workout_from_underarmourdata(request.user,underarmourid,data)
|
||||||
|
w = Workout.objects.get(id=id)
|
||||||
|
w.uploadedtounderarmour=underarmourid
|
||||||
|
w.save()
|
||||||
|
if message:
|
||||||
|
url = reverse(workout_edit_view,
|
||||||
|
kwargs = {
|
||||||
|
'id':id,
|
||||||
|
'message':message,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
url = reverse(workout_edit_view,
|
||||||
|
kwargs = {
|
||||||
|
'id':id,
|
||||||
|
})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Imports a workout from SportTracks
|
# Imports a workout from SportTracks
|
||||||
|
|||||||
@@ -234,6 +234,14 @@ RUNKEEPER_CLIENT_ID = CFG['runkeeper_client_id']
|
|||||||
RUNKEEPER_CLIENT_SECRET = CFG['runkeeper_client_secret']
|
RUNKEEPER_CLIENT_SECRET = CFG['runkeeper_client_secret']
|
||||||
RUNKEEPER_REDIRECT_URI = "http://rowsandall.com/runkeeper_callback"
|
RUNKEEPER_REDIRECT_URI = "http://rowsandall.com/runkeeper_callback"
|
||||||
|
|
||||||
|
# Under Armour
|
||||||
|
|
||||||
|
UNDERARMOUR_CLIENT_ID = CFG['underarmour_client_name']
|
||||||
|
UNDERARMOUR_CLIENT_SECRET = CFG['underarmour_client_secret']
|
||||||
|
UNDERARMOUR_CLIENT_KEY = CFG['underarmour_client_key']
|
||||||
|
#UNDERARMOUR_REDIRECT_URI = "https://rowsandall.com/underarmour_callback"
|
||||||
|
UNDERARMOUR_REDIRECT_URI = "http://localhost:8000/underarmour_callback"
|
||||||
|
|
||||||
# RQ stuff
|
# RQ stuff
|
||||||
|
|
||||||
RQ_QUEUES = {
|
RQ_QUEUES = {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ urlpatterns += [
|
|||||||
url(r'^call\_back',rowersviews.rower_process_callback),
|
url(r'^call\_back',rowersviews.rower_process_callback),
|
||||||
url(r'^stravacall\_back',rowersviews.rower_process_stravacallback),
|
url(r'^stravacall\_back',rowersviews.rower_process_stravacallback),
|
||||||
url(r'^sporttracks\_callback',rowersviews.rower_process_sporttrackscallback),
|
url(r'^sporttracks\_callback',rowersviews.rower_process_sporttrackscallback),
|
||||||
|
url(r'^underarmour\_callback',rowersviews.rower_process_underarmourcallback),
|
||||||
url(r'^runkeeper\_callback',rowersviews.rower_process_runkeepercallback),
|
url(r'^runkeeper\_callback',rowersviews.rower_process_runkeepercallback),
|
||||||
url(r'^twitter\_callback',rowersviews.rower_process_twittercallback),
|
url(r'^twitter\_callback',rowersviews.rower_process_twittercallback),
|
||||||
url(r'^i18n/', include('django.conf.urls.i18n')),
|
url(r'^i18n/', include('django.conf.urls.i18n')),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.three {
|
.three {
|
||||||
background-image: url("/static/img/landing1.jpg");
|
background-image: url("/static/img/landing3.jpg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.four {
|
.four {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 404 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Reference in New Issue
Block a user