Private
Public Access
1
0

Merge branch 'release/v2.10'

This commit is contained in:
Sander Roosendaal
2017-04-16 20:08:25 +02:00
19 changed files with 890 additions and 7 deletions
Binary file not shown.
+2
View File
@@ -783,6 +783,8 @@ def rdata(file,rower=rrower()):
res = rrdata(csvfile=file+'.gz',rower=rower) res = rrdata(csvfile=file+'.gz',rower=rower)
except IOError,IndexError: except IOError,IndexError:
res = 0 res = 0
except:
res = 0
return res return res
@@ -96,6 +96,7 @@ class Command(BaseCommand):
donotdelete = 1 donotdelete = 1
try: try:
dd = send_confirm(rr.user,name,link) dd = send_confirm(rr.user,name,link)
time.sleep(10)
except: except:
pass pass
+5
View File
@@ -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
+23 -2
View File
@@ -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>
+16 -3
View File
@@ -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 %}
+28
View File
@@ -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)
+370
View File
@@ -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
+5
View File
@@ -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
View File
@@ -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
+8
View File
@@ -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 = {
+1
View File
@@ -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')),
+1 -1
View File
@@ -19,7 +19,7 @@
} }
.three { .three {
background-image: url("/static/img/landing1.jpg"); background-image: url("/static/img/landing2.jpg");
} }
.four { .four {
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 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: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB