Private
Public Access
1
0

removed UnderArmour / MapMyFitness

This commit is contained in:
Sander Roosendaal
2021-05-21 09:36:38 +02:00
parent 3d208527ee
commit b58ba932fd
22 changed files with 54 additions and 1527 deletions

View File

@@ -46,8 +46,6 @@ class RowerInline(admin.StackedInline):
'sporttrackstoken','sporttrackstokenexpirydate',
'sporttracksrefreshtoken',
'sporttracks_auto_export',
'underarmourtoken','underarmourtokenexpirydate',
'underarmourrefreshtoken',
'mapmyfitness_auto_export',
'tptoken','tptokenexpirydate','tprefreshtoken',
'trainingpeaks_auto_export',

View File

@@ -22,15 +22,7 @@ from scipy import optimize
from json.decoder import JSONDecodeError
from pytz.exceptions import UnknownTimeZoneError
def dologging(s):
tstamp = time.localtime()
timestamp = time.strftime('%b-%d-%Y %H:%M:%S', tstamp)
with open('debuglog.log','a') as f:
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write(s)
from rowers.utils import dologging
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
@@ -39,6 +31,7 @@ from rowsandall_app.settings import (
from rowers.tasks import (
handle_c2_import_stroke_data, handle_c2_sync, handle_c2_async_workout,
handle_c2_getworkout
)
import django_rq
queue = django_rq.get_queue('default')
@@ -840,64 +833,15 @@ def get_workout(user,c2id,do_async=False):
elif (timezone.now()>r.tokenexpirydate):
s = "Token expired. Needs to refresh."
return custom_exception_handler(401,s),0
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.c2token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://log.concept2.com/api/users/me/results/"+str(c2id)
s = requests.get(url,headers=headers)
if s.status_code != 200: # pragma: no cover
if s.status_code == 404:
raise PermissionDenied("You have no access to this resource")
else:
s = "Something went wrong with the import"
return custom_exception_handler(401,s), 0
job = myqueue(queuehigh,
handle_c2_getworkout,
user.id,
r.c2token,
c2id,
r.defaulttimezone)
data = s.json()['data']
alldata = {c2id:data}
splitdata = None
#with open('c2temp.json','w') as f:
# f.write(json.dumps(s.json()))
# print(s.json())
if do_async:
print('aap',alldata)
job = myqueue(queuehigh,
handle_c2_async_workout,
alldata,
r.user.id,
r.c2token,
c2id,
0,
r.defaulttimezone)
return data, pd.DataFrame()
if 'workout' in data:
if 'splits' in data['workout']: # pragma: no cover
splitdata = data['workout']['splits']
elif 'intervals' in data['workout']: # pragma: no cover
splitdata = data['workout']['intervals']
else: # pragma: no cover
splitdata = None
# Check if workout has stroke data, and get the stroke data
if data['stroke_data']:
res2 = get_c2_workout_strokes(user,c2id)
if res2.status_code == 200:
strokedata = pd.DataFrame.from_dict(res2.json()['data'])
else: # pragma: no cover
strokedata = pd.DataFrame()
else: # pragma: no cover
strokedata = pd.DataFrame()
return data,strokedata
return 1
# Get stroke data belonging to C2 ID
def get_c2_workout_strokes(user,c2id):
@@ -1027,9 +971,9 @@ def workout_c2_upload(user,w,asynchron=False):
if not c2userid: # pragma: no cover
raise NoTokenError("User has no token")
dologging('Upload to C2 user {userid}'.format(userid=user.id))
dologging('debuglog.log','Upload to C2 user {userid}'.format(userid=user.id))
data = createc2workoutdata(w)
dologging(json.dumps(data))
dologging('debuglog.log',json.dumps(data))
if data == 0: # pragma: no cover
return "Error: No data file. Contact info@rowsandall.com if the problem persists",0

View File

@@ -988,10 +988,6 @@ class Rower(models.Model):
sporttracksrefreshtoken = models.CharField(default='',max_length=200,
blank=True,null=True)
sporttracks_auto_export = models.BooleanField(default=False)
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)
mapmyfitness_auto_export = models.BooleanField(default=False)
tptoken = models.CharField(default='',max_length=1000,blank=True,null=True)
tptokenexpirydate = models.DateTimeField(blank=True,null=True)
@@ -3201,7 +3197,6 @@ class Workout(models.Model):
maxhr = models.BigIntegerField(blank=True,null=True)
uploadedtostrava = models.BigIntegerField(default=0)
uploadedtosporttracks = models.BigIntegerField(default=0)
uploadedtounderarmour = models.BigIntegerField(default=0)
uploadedtotp = models.BigIntegerField(default=0)
uploadedtorunkeeper = models.BigIntegerField(default=0)
uploadedtogarmin = models.BigIntegerField(default=0)
@@ -3283,7 +3278,6 @@ class TombStone(models.Model):
uploadedtoc2 = models.IntegerField(default=0)
uploadedtostrava = models.BigIntegerField(default=0)
uploadedtosporttracks = models.BigIntegerField(default=0)
uploadedtounderarmour = models.BigIntegerField(default=0)
uploadedtotp = models.BigIntegerField(default=0)
uploadedtorunkeeper = models.BigIntegerField(default=0)
uploadedtonk = models.BigIntegerField(default=0)
@@ -3294,7 +3288,6 @@ def create_tombstone_on_delete(sender, instance, **kwargs):
user=instance.user,
uploadedtoc2 = instance.uploadedtoc2,
uploadedtostrava = instance.uploadedtostrava,
uploadedtounderarmour = instance.uploadedtounderarmour,
uploadedtotp = instance.uploadedtotp,
uploadedtorunkeeper = instance.uploadedtorunkeeper,
uploadedtonk = instance.uploadedtonk

View File

@@ -25,6 +25,8 @@ from rowingdata import rowingdata as rdata
from datetime import timedelta
from sqlalchemy import create_engine
from rowers.imports import splituadata
#from celery import app
from rowers.celery import app
from celery import shared_task
@@ -64,7 +66,7 @@ from django_rq import job
from django.utils import timezone
from django.utils.html import strip_tags
from rowers.utils import deserialize_list,ewmovingaverage,wavg
from rowers.utils import deserialize_list,ewmovingaverage,wavg,dologging
from rowers.emails import htmlstrip
from rowers import mytypes
@@ -125,15 +127,6 @@ from rowers.courseutils import (
InvalidTrajectoryError
)
def dologging(s):
tstamp = time.localtime()
timestamp = time.strftime('%b-%d-%Y %H:%M:%S', tstamp)
with open('debuglog.log','a') as f:
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write(s)
# Concept2 logbook sends over split data for each interval
# We use it here to generate a custom summary
# Some users complained about small differences
@@ -2985,7 +2978,23 @@ def handle_nk_async_workout(alldata,userid,nktoken,nkid,delaysec,defaulttimezone
# return
return workoutid
@app.task
def handle_c2_getworkout(userid,c2token,c2id,defaulttimezone,debug=False,**kwargs):
authorizationstring = str('Bearer ' + c2token)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://log.concept2.com/api/users/me/results/"+str(c2id)
s = requests.get(url,headers=headers)
if s.status_code != 200: # pragma: no cover
return 0
data = s.json()['data']
alldata = {c2id:data}
splitdata = None
return handle_c2_async_workout(alldata,userid,c2token,c2id,0,defaulttimezone)
@app.task
def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone,debug=False,**kwargs):
@@ -3007,8 +3016,8 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
weightclass = data['weight_class']
s = 'User {userid}, C2 ID {c2id}'.format(userid=userid,c2id=c2id)
dologging(s)
dologging(json.dumps(data))
dologging('debuglog.log',s)
dologging('debuglog.log',json.dumps(data))
try:
title = data['name']
@@ -3041,7 +3050,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
s = 'Time zone {timezone}, stardatetime {startdatetime}, duration {duration}'.format(
timezone=timezone,startdatetime=startdatetime,
duration=duration)
dologging(s)
dologging('debuglog.log',s)
@@ -3246,6 +3255,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
return workoutid
@app.task
def fetch_strava_workout(stravatoken,oauth_data,stravaid,csvfilename,userid,debug=False,**kwargs):
fetchresolution = 'high'

View File

@@ -1,209 +0,0 @@
{% extends "base.html" %}
{% load static %}
{% load rowerfilters %}
{% block title %}Export {% endblock %}
{% block content %}
<div id="exportbuttons" class="grid_6 alpha">
<h3>Export Workout</h3>
<div class="grid_2 alpha">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/edit">Edit Workout</a>
</p>
</div>
<div class="grid_2">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/workflow">Workflow View</a>
</p>
</div>
<div class="grid_2 omega">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/advanced">Advanced Edit</a>
</p>
</div>
<div class="grid_6 alpha">
<p>
Click on the icon to upload this workout to your site of choice. A checkmark indicates that the workout has already been uploaded. If the button is grayed out, click it to authorize the connection to that site. Use TCX or CSV export to email a TCX or CSV file of your workout to yourself.
</p>
</div>
{% if workout.uploadedtoc2 == 0 %}
{% if user.rower.c2token == None or user.rower.c2token == '' %}
<div class="grid_1 alpha">
<a href="/rowers/me/c2authorize">
<img src="/static/img/c2square_gray.png" alt="C2 icon" width="60" height="60"></a>
</div>
{% else %}
<div class="grid_1 alpha">
<a href="/rowers/workout/{{ workout.id|encode }}/c2uploadw"><img src="/static/img/c2square.jpg" alt="Concept2 icon" width="60" height="60"></a>
</div>
{% endif %}
{% else %}
<div class="grid_1 alpha">
<a href="http://log.concept2.com/profile/{{ c2userid }}/log/{{ workout.uploadedtoc2 }}">
<img src="/static/img/c2square_checked.png" alt="Concept2 icon" width="60" height="60"></a>
</div>
{% endif %}
{% if workout.uploadedtostrava == 0 %}
{% if user.rower.stravatoken == None or user.rower.stravatoken == '' %}
<div class="grid_1">
<a href="/rowers/me/stravaauthorize">
<img src="/static/img/stravasquare_gray.png" alt="Strava icon" width="60" height="60"></a>
</div>
{% else %}
<div class="grid_1">
<a href="/rowers/workout/{{ workout.id|encode }}/stravauploadw"><img src="/static/img/stravasquare.png" alt="Strava icon" width="60" height="60"></a>
</div>
{% endif %}
{% else %}
<div class="grid_1">
<a href="https://www.strava.com/activities/{{ workout.uploadedtostrava }}">
<img src="/static/img/stravasquare_checked.png" alt="Concept2 icon" width="60" height="60"></a>
</div>
{% endif %}
{% if workout.uploadedtosporttracks == 0 %}
{% if user.rower.sporttrackstoken == None or user.rower.sporttrackstoken == '' %}
<div class="grid_1">
<a href="/rowers/me/sporttracksauthorize">
<img src="/static/img/sporttrackssquare_gray.png" alt="SportTracks icon" width="60" height="60"></a>
</div>
{% else %}
<div class="grid_1">
<a href="/rowers/workout/{{ workout.id|encode }}/sporttracksuploadw">
<img src="/static/img/sporttrackssquare.png" alt="SportTracks icon" width="60" height="60"></a>
</div>
{% endif %}
{% else %}
<div class="grid_1">
<a href="https://sporttracks.mobi/activity/{{ workout.uploadedtosporttracks }}">
<img src="/static/img/sporttrackssquare_checked.png" alt="Concept2 icon" width="60" height="60"></a>
</div>
{% endif %}
<div class="grid_1">
<a href="/rowers/workout/{{ workout.id|encode }}/emailtcx">
<img src="/static/img/export.png" alt="TCX Export" width="60" height="60"></a>
</div>
<div class="grid_1">
<a href="/rowers/workout/{{ workout.id|encode }}/emailcsv">
<img src="/static/img/CSVsquare.png" alt="CSV Export" width="60" height="60"></a>
</div>
<div class="grid_6 alpha">
{% if workout.uploadedtorunkeeper == 0 %}
{% if user.rower.runkeepertoken == None or user.rower.runkeepertoken == '' %}
<div class="grid_1 alpha">
<a href="/rowers/me/runkeeperauthorize">
<img src="/static/img/rkgray.png" alt="Runkeeper icon" width="60" height="60"></a>
</div>
{% else %}
<div class="grid_1 alpha">
<a href="/rowers/workout/{{ workout.id|encode }}/runkeeperuploadw"><img src="/static/img/rksquare.png" alt="Runkeeper icon" width="60" height="60"></a>
</div>
{% endif %}
{% else %}
<div class="grid_1 alpha">
<a href="https://runkeeper.com/user/{{ rkuserid }}/activity/{{ workout.uploadedtorunkeeper }}">
<img src="/static/img/rkchecked.png" alt="Runkeeper icon" width="60" height="60"></a>
</div>
{% 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|encode }}/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 %}
{% if workout.uploadedtotp == 0 %}
{% if user.rower.tptoken == None or user.rower.tptoken == '' %}
<div class="grid_1">
<a href="/rowers/me/tpauthorize">
<img src="/static/img/tpgray.png" alt="TrainingPeaks icon" width="60" height="60"></a>
</div>
{% else %}
<div class="grid_1">
<a href="/rowers/workout/{{ workout.id|encode }}/tpuploadw"><img src="/static/img/tpicon.png" alt="Tp icon" width="60" height="60"></a>
</div>
{% endif %}
{% else %}
<div class="grid_1">
<a href="https://app.trainingpeaks.com">
<img src="/static/img/tpchecked.png" alt="TrainingPeaks icon" width="60" height="60"></a>
</div>
{% endif %}
<div class="grid_1 omega">
<a href="/rowers/workout/{{ workout.id|encode }}/emailgpx">
<img src="/static/img/gpx.jpg" alt="GPX Export" width="60" height="60"></a>
</div>
</div>
</div>
<div class="grid_6 omega">
<h3>Connect</h3>
<div class="grid_6">
<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
revoke the authorization for the "rowingdata" app.</p>
<div class="grid_2 alpha">
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
</div>
<div class="grid_2">
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
</div>
<div class="grid_2 omega">
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
</div>
</div>
<div class="grid_6">
<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>
</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 class="grid_2 omega">
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png" alt="connect with TrainingPeaks" width="150"></a></p>
</div>
</div>
<div class="grid_6 alpha">
<p>
<h2>Export Settings</h2>
<div class="grid_2 suffix_4 alpha">
<a class="button gray small" href="/rowers/me/exportsettings">Manage Export Settings</a>
</div>
</p>
</div>
</div>
{% endblock %}

View File

@@ -1,137 +0,0 @@
{% extends "base.html" %}
{% block title %}Import Workouts{% endblock title %}
{% block content %}
<div class="grid_6 alpha">
<h2>Import Workouts</h2>
<div class="grid_6">
<div class="grid_3 alpha">
<p>
<a href="/rowers/workout/stravaimport"><img src="/static/img/stravalogo.png" alt="strava logo" width="140"></a>
</p>
</div>
<div class="grid_3 omega">
<p>Import workouts from Strava</p>
</div>
</div>
<div class="grid_6">
<div class="grid_3 alpha">
<p>
<a href="/rowers/workout/c2list"><img src="/static/img/blueC2logo.png" alt="Concept2 logo" width="140"></a>
</p>
</div>
<div class="grid_3 omega">
<p>Import workouts from the Concept2 logbook</p>
</div>
<div class="grid_3 alpha">
<p>
<a href="/rowers/workout/sporttracksimport"><img src="/static/img/sporttracks-button.png" alt="SportTracks logo" width="140"></a>
</p>
</div>
<div class="grid_3 omega">
<p>Import workouts from SportTracks</p>
</div>
</div>
<div class="grid_6">
<div class="grid_3 alpha">
<p>
<a href="/rowers/workout/runkeeperimport"><img src="/static/img/rk-logo.png" alt="Runkeeper logo" width="140"></a>
</p>
</div>
<div class="grid_3 omega">
<p>Import workouts from RunKeeper</p>
</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 class="grid_6">
<div class="grid_3 alpha">
<p>
<a href="/rowers/workout/polarimport"><img src="/static/img/Polar_connectwith_btn_white.png" alt="Polar logo" width="140"></a>
</p>
</div>
<div class="grid_3 omega">
<p>Import workouts from Polar Flow. </p><p><b>Note: No workout selection possible. Automatically imports all new workouts</b></p>
</div>
</div>
</div>
<div class="grid_6 omega">
<h2>Connect</h2>
<div class="grid_6 alpha">
<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
revoke the authorization for the "rowingdata" app.</p>
<div class="grid_2 alpha">
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
</div>
<div class="grid_2">
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
</div>
<div class="grid_2 omega">
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
</div>
</div>
<div class="grid_6 alpha">
<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>
</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 class="grid_2 omega">
<p><a href="/rowers/me/polarauthorize/"><img src="/static/img/Polar_connectwith_btn_white.png"
alt="connect with Polar" width="130"></a></p>
</div>
</div>
<h2>Auto Import/Export Settings</h2>
<p>Use the form below to set your auto import/export settings.
As we implement auto import/export for various partner sites, this form will be expanded.</p>
<p>Auto Import = rowsandall.com will regularly poll the partner site for new
workouts and automatically import new workouts
</p>
<p>Auto Export = New workouts uploaded to rowsandall.com will be automatically synchronized
to the partner site</p>
<p>These settings only have effect if you are a user on one
of the <a href="/rowers/paidplans">paid plans</a></p>
<div class="grid_6 alpha">
<form id="importexportform" method="post">
<table width=70%>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton">
<input type="submit" value="Save">
</div>
</form>
</div>
</div>
{% endblock content %}

View File

@@ -39,7 +39,6 @@
<li id="strava"><a href="/rowers/workout/stravaimport/">Strava</a></li>
<li id="runkeeper"><a href="/rowers/workout/runkeeperimport/">RunKeeper</a></li>
<li id="sporttracks"><a href="/rowers/workout/sporttracksimport/">SportTracks</a></li>
<li id="mapmyfitness"><a href="/rowers/workout/underarmourimport/">MapMyFitness</a></li>
<li id="polar"><a href="/rowers/workout/polarimport/">Polar</a></li>
</ul>
</li>

View File

@@ -207,21 +207,6 @@
</a>
{% endif %}
</li>
<li id="export-mmf">
{% if workout.uploadedtounderarmour %}
<a href="https://www.mapmyfitness.com/workout/{{ workout.uploadedtounderarmour }}">
MapMyFitness <i class="fas fa-check"></i>
</a>
{% elif user.rower.underarmourtoken == None or user.rower.underarmourtoken == '' %}
<a href="/rowers/me/underarmourauthorize">
Connect to MapMyFitness
</a>
{% else %}
<a href="/rowers/workout/{{ workout.id|encode }}/underarmouruploadw/">
MapMyFitness
</a>
{% endif %}
</li>
<li id="export-tp">
{% if workout.uploadedtotp %}
<a href="https://app.trainingpeaks.com">

View File

@@ -42,7 +42,6 @@
<li id="strava"><a href="/rowers/workout/stravaimport/">Strava</a></li>
<li id="runkeeper"><a href="/rowers/workout/runkeeperimport/">RunKeeper</a></li>
<li id="sporttracks"><a href="/rowers/workout/sporttracksimport/">SportTracks</a></li>
<li id="mapmyfitness"><a href="/rowers/workout/underarmourimport/">MapMyFitness</a></li>
<li id="polar"><a href="/rowers/workout/polarimport/">Polar</a></li>
<li id="rp3"><a href="/rowers/workout/rp3import/">RP3</a></li>
</ul>

View File

@@ -17,9 +17,6 @@
{% if rower.sporttrackstoken is not None and rower.sporttrackstoken != '' %}
SportTracks,
{% endif %}
{% if rower.underarmourtoken is not None and rower.underarmourtoken != '' %}
Under Armour (MapMyFitness),
{% endif %}
{% if rower.tptoken is not None and rower.tptoken != '' %}
TrainingPeaks,
{% endif %}
@@ -66,7 +63,7 @@
automatically auto-sync workouts from Garmin to Rowsandall (but not in the other direction). If you
want to export our structured workout sessions to your Garmin device, you have to set the "Garmin Activity"
to a activity type that is supported by your watch. Not all watches support "Custom" activities, so
you may have to set your activity to Run or Ride while rowing.
you may have to set your activity to Run or Ride while rowing.
</p>
<p>
Strava Auto Import also imports activity changes on Strava to Rowsandall, except when you delete
@@ -80,7 +77,6 @@
<p><a href="/rowers/me/nkauthorize/"><img src="/static/img/NKLiNKLogbook.png" alt="connect with NK Logbook" 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/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
<p><a href="/rowers/me/polarauthorize/"><img src="/static/img/Polar_connectwith_btn_white.png"
alt="connect with Polar" width="130"></a></p>
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png"

View File

@@ -1,41 +0,0 @@
{% extends "newbase.html" %}
{% load static %}
{% load rowerfilters %}
{% block title %}Workouts{% endblock %}
{% block main %}
<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 %}
{% block sidebar %}
{% include 'menu_workouts.html' %}
{% endblock %}

View File

@@ -1149,109 +1149,6 @@ class RunKeeperObjects(DjangoTestCase):
@pytest.mark.django_db
@override_settings(TESTING=True)
class UAObjects(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.underarmourtoken = '12'
self.r.underarmourrefreshtoken = '12'
self.r.underarmourtokenexpirydate = arrow.get(datetime.datetime.now()+datetime.timedelta(days=1)).datetime
self.r.save()
self.c.login(username='john',password='koeinsloot')
self.nu = datetime.datetime.now()
filename = 'rowers/tests/testdata/testdata.csv'
rr = rrower(hrmax=self.r.max,hrut2=self.r.ut2,
hrut1=self.r.ut1,hrat=self.r.at,
hrtr=self.r.tr,hran=self.r.an,ftp=self.r.ftp)
row = rdata(csvfile=filename,rower=rr)
totaldist = row.df['cum_dist'].max()
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
totaltime = totaltime+row.df.loc[:,' ElapsedTime (sec)'].iloc[0]
hours = int(totaltime/3600.)
minutes = int((totaltime - 3600.*hours)/60.)
seconds = int(totaltime - 3600.*hours - 60.*minutes)
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
self.w = Workout.objects.create(
name='testworkout',workouttype='water',
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
starttime=workoutstarttime,
startdatetime=row.rowdatetime,
duration=duration,distance=totaldist,
csvfilename=filename
)
@patch('rowers.imports.requests.post', side_effect=mocked_requests)
def test_underarmour_callback(self, mock_post):
response = self.c.get('/underarmour_callback?code=dsdoij232s',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.underarmourstuff.requests.post', side_effect=mocked_requests)
def test_underarmour_token_refresh(self, mock_post):
response = self.c.get('/rowers/me/underarmourrefresh/',follow=True)
self.assertEqual(response.status_code, 200)
@patch('rowers.underarmourstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.underarmourstuff.requests.get', side_effect=mocked_requests)
def test_underarmour_upload(self, mock_get, mock_post):
response = self.c.get('/rowers/workout/'+encoded1+'/underarmouruploadw/')
self.assertRedirects(response,
expected_url = '/rowers/workout/'+encoded1+'/edit/',
status_code=302,target_status_code=200)
self.assertEqual(response.url, '/rowers/workout/'+encoded1+'/edit/')
self.assertEqual(response.status_code, 302)
@patch('rowers.underarmourstuff.requests.get', side_effect=mocked_requests)
def test_underarmour_list(self, mock_get):
response = self.c.get('/rowers/workout/underarmourimport',follow=True)
self.assertEqual(response.status_code,200)
@patch('rowers.imports.requests.get', side_effect=mocked_requests)
@patch('rowers.dataprep.create_engine')
def test_underarmour_import(self, mock_get, mocked_sqlalchemy):
response = self.c.get('/rowers/workout/underarmourimport/12/',follow=True)
self.assertRedirects(response,
expected_url='/rowers/workout/'+encoded2+'/edit/',
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
#@pytest.mark.django_db
@override_settings(TESTING=True)
class TPObjects(DjangoTestCase):

View File

@@ -99,7 +99,6 @@ class TraverseLinksTest(TestCase):
'.*authorize.*',
'.*youtu.*',
'.*earth.*',
'.*underarmour.*',
'.*runkeeper.*',
'.*c2list.*',
'.*stravaimport.*',

View File

@@ -1,544 +0,0 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
from rowers.imports import *
import numpy
import rowers.mytypes as mytypes
from rowers.mytypes import otwtypes
from rowers.rower_rules import is_workout_user
from rowsandall_app.settings import (
UNDERARMOUR_CLIENT_KEY,
UNDERARMOUR_CLIENT_SECRET,
UNDERARMOUR_REDIRECT_URI,
)
oauth_data = {
'client_id': UNDERARMOUR_CLIENT_KEY,
'client_secret': UNDERARMOUR_CLIENT_SECRET,
'redirect_uri': UNDERARMOUR_REDIRECT_URI,
'autorization_uri': "https://www.mapmyfitness.com/v7.1/oauth2/uacf/authorize/",
'content_type': 'application/x-www-form-urlencoded',
'tokenname': 'underarmourtoken',
'refreshtokenname': 'underarmourrefreshtoken',
'expirydatename': 'underarmourtokenexpirydate',
'bearer_auth': True,
'base_url': "https://api.ua.com/v7.1/oauth2/access_token/",
'scope':'write',
}
# Checks if user has UnderArmour token, renews them if they are expired
def underarmour_open(user):
return imports_open(user,oauth_data)
# Refresh ST token using refresh token
def do_refresh_token(refreshtoken,access_token):
return imports_do_refresh_token(
refreshtoken,oauth_data,access_token=access_token
)
# Exchange access code for long-lived access token
def get_token(code):
return imports_get_token(code,oauth_data)
# Make authorization URL including random string
def make_authorization_url(request):
return imports_make_authorization_url(oauth_data) # pragma: no cover
# 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))+"&order_by=-start_datetime"
s = requests.get(url,headers=headers)
return s
# Get workout summary data by Underarmour ID
def get_workout(user,underarmourid,do_async=False):
r = Rower.objects.get(user=user)
if (r.underarmourtoken == '') or (r.underarmourtoken is None): # pragma: no cover
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)
data = s.json()
strokedata = pd.DataFrame.from_dict({
key: pd.Series(value) for key, value in data.items()
})
return data,strokedata
# Create Workout Data for upload to Underarmour
def createunderarmourworkoutdata(w):
filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except: # pragma: no cover
return 0
st = w.startdatetime.astimezone(pytz.timezone(w.timezone))
start_time = st.isoformat()
averagehr = int(row.df[' HRCur (bpm)'].mean())
minhr = int(row.df[' HRCur (bpm)'].min())
maxhr = int(row.df[' HRCur (bpm)'].max())
averagespm = int(row.df[' Cadence (stokes/min)'].mean()/2.)
minspm = int(row.df[' Cadence (stokes/min)'].min()/2.)
maxspm = int(row.df[' Cadence (stokes/min)'].max()/2.)
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
try:
notes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
notes = 'from '+w.workoutsource+' via rowsandall.com'
# adding diff, trying to see if this is valid
#t = row.df.loc[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)']
t = row.df.loc[:,'TimeStamp (sec)'].values #-row.df.ix[0,'TimeStamp (sec)']
# t += arrow.get(st).timestamp()
# t[0] = t[1]
d = row.df.loc[:,'cum_dist'].values
d[0] = d[1]
t = t.astype(float).tolist()
d = d.astype(int).tolist()
spm = row.df[' Cadence (stokes/min)'].astype(int).tolist()
spm[0] = spm[1]
hr = row.df[' HRCur (bpm)'].astype(int).tolist()
speed = row.df[' AverageBoatSpeed (m/s)']
speedmin = float(row.df[' AverageBoatSpeed (m/s)'].min())
speedmax = float(row.df[' AverageBoatSpeed (m/s)'].max())
speedmean = float(row.df[' AverageBoatSpeed (m/s)'].mean())
speed = speed.replace(np.inf,0).tolist()
haslatlon=1
try: # pragma: no cover
lat = row.df[' latitude']
lon = row.df[' longitude']
if not lat.std() and not lon.std():
haslatlon = 0
except KeyError:
haslatlon = 0
# path data
if haslatlon: # pragma: no cover
locdata = []
for e in zip(t,lat.values,lon.values):
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]])
timeseries = {
"distance": distancedata,
"heartrate": hrdata,
"cadence": spmdata,
}
aggregates = {
"elapsed_time_total": int(duration),
"active_time_total": int(duration),
"distance_total": int(max(d)),
"heartrate_avg": averagehr,
"heart_rate_min": minhr,
"heart_rate_max": maxhr,
"speed_min": speedmin,
"speed_max": speedmax,
"speed_avg": speedmean,
"cadence_min": minspm,
"cadence_max": maxspm,
"cadence_avg": averagespm,
}
if haslatlon: # pragma: no cover
timeseries["position"] = locdata
data = {
"start_datetime": start_time,
"name": name,
"has_time_series": True,
"time_series": timeseries,
"aggregates": aggregates,
"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 = response.json()
links = t["_links"]
id = links["self"][0]["id"]
return int(id)
def refresh_ua_actlist(user): # pragma: no cover
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'}
url = "https://api.ua.com/v7.1/activity_type/"
response = requests.get(url,headers=headers)
me_json = response.json()
types = me_json["_embedded"]["activity_types"]
w = {int(t["_links"]["self"][0]["id"]):t["name"] for t in types}
wdf = pd.Series(w,name='Name')
wdf.to_csv('static/rigging/ua2.csv',index_label='id',header=True)
return w
try:
activities = pd.read_csv('static/rigging/ua2.csv',index_col='id')
actdict = activities.to_dict()['Name']
except: # pragma: no cover
actdict = {}
def get_typefromid(typeid,user):
r = Rower.objects.get(user=user)
try:
res = actdict[int(typeid)]
except KeyError: # pragma: no cover
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/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'}
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: # pragma: no cover
res = 0
return res
def default(o): # pragma: no cover
if isinstance(o, numpy.int64): return int(o)
raise TypeError
def workout_ua_upload(user,w): # pragma: no cover
message = "Uploading to MapMyFitness"
uaid = 0
r = w.user
thetoken = underarmour_open(r.user)
# ready to upload. Hurray
if (is_workout_user(user,w)):
data = createunderarmourworkoutdata(w)
# return HttpResponse(json.dumps(data))
if not data:
message = "Data error"
uaid = 0
return message, uaid
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'Api-Key': UNDERARMOUR_CLIENT_KEY,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json',
}
url = "https://api.ua.com/v7.1/workout/"
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
# check for duplicate error first
if (response.status_code == 409 ):
message = "Duplicate error"
w.uploadedtounderarmour = -1
uaid = -1
w.save()
elif (response.status_code == 201 or response.status_code==200):
uaid = getidfromresponse(response)
w.uploadedtounderarmour = uaid
w.save()
return 'Successfully synchronized with MapMyFitness',uaid
else:
s = response
message = "Something went wrong in workout_underarmour_upload_view: %s - %s" % (s.reason,s.text)
uaid = 0
return message, uaid
else:
message = "You are not authorized to upload this workout"
uaid = 0
return message, uaid
return message, uaid
# Create workout from SportTracks Data, which are slightly different
# than Strava or Concept2 data
def add_workout_from_data(user,importid,data,strokedata,
source='mapmyfitness',
workoutsource='mapmyfitness'):
workouttype = 'water'
try:
comments = data['notes']
except: # pragma: no cover
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: # pragma: no cover
try:
rowdatetime = datetime.strptime(data['start_datetime'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
try:
rowdatetime = parser.parse(data['start_datetime'])
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
rowdatetime = datetime.strptime(data['date'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
starttimeunix = arrow.get(rowdatetime).timestamp()
try:
title = data['name']
except: # pragma: no cover
title = "Imported data"
timeseries = data['time_series']
# position, distance, speed, cadence, power,
try:
res = splituadata(timeseries['distance'])
distance = res[1]
times_distance = res[0]
except KeyError: # pragma: no cover
message = "Error. No distance data"
return (0,message)
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: # pragma: no cover
times_location = times_distance
latcoord = np.zeros(len(times_distance))
loncoord = np.zeros(len(times_distance))
if workouttype in otwtypes:
workouttype = 'rower'
try:
res = splituadata(timeseries['cadence'])
times_spm = res[0]
spm = res[1]
except KeyError: # pragma: no cover
times_spm = times_distance
spm = 0*times_distance
try:
res = splituadata(timeseries['heartrate'])
hr = res[1]
times_hr = res[0]
except KeyError: # pragma: no cover
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[0] = starttimeunix
df['TimeStamp (sec)'] = unixtime
dt = np.diff(cum_time).mean()
wsize = round(5./dt)
df = df.fillna(0)
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
id,message = dataprep.save_workout_database(csvfilename,r,
workouttype=workouttype,
workoutsource='mapmyfitness',
dosmooth=r.dosmooth,
title=title,
notes=comments)
return (id,message)

View File

@@ -125,7 +125,6 @@ def matchsync(line):
tester4 = tester+'(.*)(strava)'
tester5 = tester+'(.*)((st)|(sporttracks))'
tester6 = tester+'(.*)((rk)|(runkeeper))'
tester7 = tester+'(.*)((mapmyfitness)|(underarmour)|(ua))'
tester = re.compile(tester)
@@ -262,8 +261,6 @@ def getsyncoptions(uploadoptions,values): # pragma: no cover
uploadoptions['upload_to_SportTracks'] = True
if v in ['rk','runkeeper']:
uploadoptions['upload_to_RunKeeper'] = True
if v in ['ua','underarmour','mapmyfitness']:
uploadoptions['upload_to_MapMyFitness'] = True
except AttributeError:
pass
@@ -491,7 +488,7 @@ import rowers.c2stuff as c2stuff
import rowers.stravastuff as stravastuff
import rowers.sporttracksstuff as sporttracksstuff
import rowers.runkeeperstuff as runkeeperstuff
import rowers.underarmourstuff as underarmourstuff
import rowers.tpstuff as tpstuff
from rowers.rower_rules import is_promember
@@ -653,14 +650,6 @@ def do_sync(w,options, quick=False):
message = "Please connect to Runkeeper first"
id = 0
if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export): # pragma: no cover
try:
message,id = underarmourstuff.workout_ua_upload(
w.user.user,w
)
except NoTokenError:
message = "Please connect to MapMyFitness first"
id = 0
if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export): # pragma: no cover

View File

@@ -510,14 +510,11 @@ urlpatterns = [
re_path(r'^workout/polarimport/user/(?P<userid>\d+)/',views.workout_polarimport_view,name='workout_polarimport_view'),
re_path(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'),
re_path(r'^workout/runkeeperimport/user/(?P<userid>\d+)/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'),
re_path(r'^workout/underarmourimport/user/(?P<userid>\d+)/$',views.workout_underarmourimport_view,name='workout_underarmourimport_view'),
re_path(r'^workout/underarmourimport/$',views.workout_underarmourimport_view,name='workout_underarmourimport_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/c2uploadw/$',views.workout_c2_upload_view,name='workout_c2_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/stravauploadw/$',views.workout_strava_upload_view,name='workout_strava_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/recalcsummary/$',views.workout_recalcsummary_view,name='workout_recalcsummary_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/sporttracksuploadw/$',views.workout_sporttracks_upload_view,name='workout_sporttracks_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/runkeeperuploadw/$',views.workout_runkeeper_upload_view,name='workout_runkeeper_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/underarmouruploadw/$',views.workout_underarmour_upload_view,name='workout_underarmour_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/tpuploadw/$',views.workout_tp_upload_view,name='workout_tp_upload_view'),
re_path(r'^multi-compare/workout/(?P<id>\b[0-9A-Fa-f]+\b)/user/(?P<userid>\d+)/$',views.multi_compare_view,
name='multi_compare_view'),
@@ -600,12 +597,10 @@ urlpatterns = [
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/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/rp3authorize/$',views.rower_rp3_authorize,name='rower_rp3_authorize'),
re_path(r'^me/runkeeperauthorize/$',views.rower_runkeeper_authorize,name='rower_runkeeper_authorize'),
re_path(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh,name='rower_sporttracks_token_refresh'),
re_path(r'^me/underarmourrefresh/$',views.rower_underarmour_token_refresh,name='rower_underarmoud_token_refresh'),
re_path(r'^me/tprefresh/$',views.rower_tp_token_refresh,name='rower_tp_token_refresh'),
re_path(r'^me/c2refresh/$',views.rower_c2_token_refresh,name='rower_c2_token_refresh'),
re_path(r'^me/favoritecharts/$',views.rower_favoritecharts_view,name='rower_favoritecharts_view'),

View File

@@ -105,6 +105,15 @@ info_calls = [
import random
def dologging(filename,s):
tstamp = time.localtime()
timestamp = time.strftime('%b-%d-%Y %H:%M:%S', tstamp)
with open('filename','a') as f:
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write(s)
def to_pace(pace):
minutes, seconds = divmod(pace,60)
seconds, rest = divmod(seconds, 1)

View File

@@ -286,66 +286,6 @@ def workout_runkeeper_upload_view(request,id=0):
return HttpResponseRedirect(url) # pragma: no cover
# Upload workout to Underarmour
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_underarmour_upload_view(request,id=0):
message = ""
w = get_workout(id)
r = w.user
try:
thetoken = underarmour_open(r.user)
except NoTokenError: # pragma: no cover
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
# ready to upload. Hurray
data = underarmourstuff.createunderarmourworkoutdata(w)
if not data: # pragma: no cover
message = "Data error"
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
return HttpResponseRedirect(url)
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'Api-Key': UNDERARMOUR_CLIENT_KEY,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json',
}
url = "https://api.ua.com/v7.1/workout/"
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
# check for duplicate error first
if (response.status_code == 409 ): # pragma: no cover # pragma: no cover
message = "Duplicate error"
messages.error(request,message)
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 = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url)
else: # pragma: no cover
s = response
message = "Something went wrong in workout_underarmour_upload_view: %s " % s.reason
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
}) # pragma: no cover
return HttpResponseRedirect(url) # pragma: no cover
# Upload workout to SportTracks
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid)
@@ -525,24 +465,8 @@ def rower_sporttracks_authorize(request): # pragma: no cover
return HttpResponseRedirect(url)
# Underarmour Authorization
@login_required()
def rower_underarmour_authorize(request): # pragma: no cover
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
state = str(uuid4())
redirect_uri = UNDERARMOUR_REDIRECT_URI
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)
# Underarmour Authorization
# RP3 Authorization
@login_required()
def rower_rp3_authorize(request): # pragma: no cover
# Generate a random string for the state parameter
@@ -557,7 +481,7 @@ def rower_rp3_authorize(request): # pragma: no cover
return HttpResponseRedirect(url)
# Underarmour Authorization
# TrainingPeaks Authorization
@login_required()
def rower_tp_authorize(request): # pragma: no cover
# Generate a random string for the state parameter
@@ -603,33 +527,6 @@ def rower_c2_token_refresh(request):
return HttpResponseRedirect(url)
# Underarmour token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_underarmour_token_refresh(request):
r = getrower(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 = getrower(request.user)
r.underarmourtoken = access_token
r.underarmourtokenexpirydate = expirydatetime
r.underarmourrefreshtoken = refresh_token
r.save()
successmessage = "Tokens refreshed. Good to go"
messages.info(request,successmessage)
url = reverse('workouts_view')
return HttpResponseRedirect(url)
# TrainingPeaks token refresh. URL for manual refresh. Not visible to users
@@ -1086,29 +983,6 @@ def rower_process_sporttrackscallback(request):
# 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 = getrower(request.user)
r.underarmourtoken = access_token
r.underarmourtokenexpirydate = expirydatetime
r.underarmourrefreshtoken = refresh_token
r.save()
successmessage = "Tokens stored. Good to go. Please check your import/export settings"
messages.info(request,successmessage)
url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url)
# Process RP3 callback
@login_required()
@@ -1680,56 +1554,6 @@ def workout_runkeeperimport_view(request,message="",userid=0):
return HttpResponse(res) # pragma: no cover
# The page where you select which RunKeeper workout to import
@login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def workout_underarmourimport_view(request,message="",userid=0):
res = underarmourstuff.get_underarmour_workout_list(request.user)
if (res.status_code != 200):
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
workouts = []
items = res.json()['_embedded']['workouts']
for item in items:
s = item['start_datetime']
i,r = underarmourstuff.get_idfromuri(request.user,item['_links'])
n = item['name']
try:
d = item['aggregates']['distance_total']
except KeyError: # pragma: no cover
d = 0
try:
ttot = item['aggregates']['active_time_total']
except KeyError:
ttot = 0
keys = ['id','distance','duration','starttime','type']
values = [i,d,ttot,s,r]
thedict = dict(zip(keys,values))
workouts.append(thedict)
rower = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':reverse('workout_c2import_view'),
'name':'Concept2'
},
]
return render(request,'underarmour_list_import.html',
{'workouts':workouts,
'breadcrumbs':breadcrumbs,
'rower':rower,
'active':'nav-workouts',
'teams':get_my_teams(request.user),
})
return HttpResponse(res) # pragma: no cover
# the page where you select which Polar workout to Import
@login_required()
@@ -2032,7 +1856,6 @@ importlistviews = {
'runkeeper':'workout_runkeeperimport_view',
'sporttracks':'workout_sporttracksimport_view',
'trainingpeaks':'workout_view',
'underarmour':'workout_underarmourimport_view',
'nk':'workout_nkimport_view',
}
@@ -2044,7 +1867,6 @@ importsources = {
'runkeeper':runkeeperstuff,
'sporttracks':sporttracksstuff,
'trainingpeaks':tpstuff,
'underarmour':underarmourstuff,
'nk':nkstuff,
}
@@ -2071,178 +1893,20 @@ def workout_getrp3importview(request,externalid):
return HttpResponseRedirect(url)
@login_required()
def workout_getimportview(request,externalid,source = 'c2',do_async=False):
data,strokedata = importsources[source].get_workout(request.user,externalid,
def workout_getimportview(request,externalid,source = 'c2',do_async=True):
result = importsources[source].get_workout(request.user,externalid,
do_async=do_async)
if do_async: # pragma: no cover
if result: # pragma: no cover
messages.info(request,"Your workout will be imported in the background")
# this should return to the respective import list page
url = reverse(importlistviews[source])
return HttpResponseRedirect(url)
if not data: # pragma: no cover
messages.error(request,"No strokedata received")
url = reverse('workouts_view')
return HttpResponseRedirect(url)
try:
workouttype = mytypes.c2mappinginv[data['type']]
except KeyError:
workouttype = 'rower'
# Now works only for C2
try:
if strokedata == 0:
messages.error(request,'An error occurred importing the workout from Concept2')
url = reverse('workouts_view')
return HttpResponseRedirect(url)
except ValueError:
pass
if strokedata.empty: # pragma: no cover
distance = data['distance']
c2id = data['id']
workouttype = mytypes.c2mappinginv[data['type']]
verified = data['verified']
startdatetime = iso8601.parse_date(data['date'])
weightclass = data['weight_class']
weightcategory = 'hwt'
if weightclass == "L":
weightcategory = 'lwt'
totaltime = data['time']/10.
duration = dataprep.totaltime_sec_to_string(totaltime)
duration = datetime.datetime.strptime(duration,'%H:%M:%S.%f').time()
try:
timezone_str = data['timezone']
except:
timezone_str = 'UTC'
if timezone_str is None:
timezone_str = 'UTC'
workoutdate = startdatetime.astimezone(
pytz.timezone(timezone_str)
).strftime('%Y-%m-%d')
starttime = startdatetime.astimezone(
pytz.timezone(timezone_str)
).strftime('%H:%M:%S')
try:
notes = data['comments']
name = notes[:40]
except (KeyError,TypeError):
comments = 'C2 Import Workout from {startdatetime}'.format(startdatetime=startdatetime)
name = notes
r = getrower(request.user)
id, message = dataprep.create_row_df(r,
distance,
duration,
startdatetime,
workouttype=workouttype)
w = Workout.objects.get(id=id)
w.uploadedtoc2 = c2id
w.name = name
w.notes = notes
w.workouttype = workouttype
w.save()
message = "This workout does not have any stroke data associated with it. We created synthetic stroke data."
messages.info(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
return HttpResponseRedirect(url)
# strokedata not empty - continue
id,message = importsources[source].add_workout_from_data(
request.user,
externalid,data,
strokedata,
source=source,
workoutsource=source)
w = get_workout(encoder.encode_hex(id))
if 'workout' in data: # pragma: no cover
if 'splits' in data['workout']:
splitdata = data['workout']['splits']
elif 'intervals' in data['workout']:
splitdata = data['workout']['intervals']
else:
splitdata = False
else:
splitdata = False
# splitdata (only for C2)
if splitdata: # pragma: no cover
w.summary,sa,results = c2stuff.summaryfromsplitdata(splitdata,data,w.csvfilename,workouttype=workouttype)
w.save()
from rowingdata.trainingparser import getlist
# set stroke data in CSV file
if sa:
values = getlist(sa)
units = getlist(sa,sel='unit')
types = getlist(sa,sel='type')
rowdata = rdata(csvfile=w.csvfilename)
if rowdata:
rowdata.updateintervaldata(values,
units,types,results)
rowdata.write_csv(w.csvfilename,gzip=True)
dataprep.update_strokedata(w.id,rowdata.df)
if source == 'strava':
w.uploadedtostrava = externalid
elif source == 'c2':
w.uploadedtoc2 = externalid
elif source == 'polar': # pragma: no cover
w.uploadedtopolar = externalid
elif source == 'runkeeper':
w.uploadedtorunkeeper = externalid
elif source == 'sporttracks':
w.uploadedtosporttracks = externalid
elif source == 'trainingpeaks': # pragma: no cover
w.uploadedtotp = externalid
elif source == 'underarmour':
w.uploadedtounderarmour = externalid
w.save()
if message: # pragma: no cover
messages.error(request,message)
r = getrower(request.user)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id)
})
messages.error(request,'Error getting the workout')
url = reverse(importlistviews[source])
return HttpResponseRedirect(url)
# Imports all new workouts from SportTracks
@login_required()
def workout_getsporttracksworkout_all(request):

View File

@@ -167,8 +167,8 @@ import rowers.garmin_stuff as garmin_stuff
from rowers.stravastuff import strava_open
import rowers.polarstuff as polarstuff
import rowers.sporttracksstuff as sporttracksstuff
import rowers.underarmourstuff as underarmourstuff
from rowers.underarmourstuff import underarmour_open
import rowers.tpstuff as tpstuff
import rowers.runkeeperstuff as runkeeperstuff
import rowers.rp3stuff as rp3stuff
@@ -180,8 +180,6 @@ from rowsandall_app.settings import (
POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET,
SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI,
SPORTTRACKS_CLIENT_SECRET,
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_REDIRECT_URI,
UNDERARMOUR_CLIENT_SECRET,UNDERARMOUR_CLIENT_KEY,
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
TP_CLIENT_ID,TP_REDIRECT_URI,TP_CLIENT_KEY,TP_CLIENT_SECRET,
RP3_CLIENT_ID,RP3_REDIRECT_URI,RP3_CLIENT_KEY,RP3_CLIENT_SECRET,

View File

@@ -329,7 +329,6 @@ def rower_exportsettings_view(request,userid=0):
'polar_auto_import':'polartoken',
'c2_auto_export':'c2token',
'c2_auto_import':'c2token',
'mapmyfitness_auto_export':'underarmourtoken',
'runkeeper_auto_export':'runkeepertoken',
'sporttracks_auto_export':'sporttrackstoken',
'strava_auto_export':'stravatoken',

View File

@@ -5270,21 +5270,6 @@ def workout_upload_view(request,
messages.error(request,message)
if (upload_to_ua): # pragma: no cover
try:
message,id = underarmourstuff.workout_ua_upload(
request.user,w
)
except NoTokenError:
message = "Please connect to MapMyFitness first"
id = 0
if id>1:
messages.info(request,message)
else:
messages.error(request,message)
if (upload_to_tp): # pragma: no cover
try:
message,id = tpstuff.workout_tp_upload(

View File

@@ -74,7 +74,6 @@ urlpatterns += [
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'^underarmour\_callback',rowersviews.rower_process_underarmourcallback),
re_path(r'^polarflowcallback',rowersviews.rower_process_polarcallback),
re_path(r'^runkeeper\_callback',rowersviews.rower_process_runkeepercallback),
re_path(r'^tp\_callback',rowersviews.rower_process_tpcallback),