Private
Public Access
1
0

rp3 in progress

This commit is contained in:
Sander Roosendaal
2021-01-26 19:29:16 +01:00
parent 4f432bef7b
commit f6d6d60e0d
5 changed files with 287 additions and 164 deletions

View File

@@ -906,14 +906,6 @@ class Rower(models.Model):
'Pwr TR',
'Pwr AN'])
hrzones = PowerZonesField(default=['Rest',
'UT2',
'UT1',
'AT',
'TR',
'AN','max'])
emailalternatives = AlternativeEmails(default=[],null=True,blank=True,verbose_name='Alternative Email addresses (separate with ",")')
# Site Settings
@@ -944,6 +936,11 @@ class Rower(models.Model):
tptokenexpirydate = models.DateTimeField(blank=True,null=True)
tprefreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True)
rp3token = models.CharField(default='',max_length=1000,blank=True,null=True)
rp3tokenexpirydate = models.DateTimeField(blank=True,null=True)
rp3refreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True)
trainingpeaks_auto_export = models.BooleanField(default=False)
polartoken = models.CharField(default='',max_length=1000,blank=True,null=True)
@@ -3662,160 +3659,6 @@ class RowerCPForm(ModelForm):
model = Rower
fields = ['cprange','kfit','kfatigue']
# Form to set rower's Power zones, including test routines
# to enable consistency
class RowerHRZonesForm(ModelForm):
hrzones = ['Rest','UT2','UT1','AT','TR','AN','Max']
hrrestname = forms.CharField(initial=hrzones[0])
hrut2name = forms.CharField(initial=hrzones[1])
hrut1name = forms.CharField(initial=hrzones[2])
hratname = forms.CharField(initial=hrzones[3])
hrtrname = forms.CharField(initial=hrzones[4])
hranname = forms.CharField(initial=hrzones[5])
hrmaxname = forms.CharField(initial=hrzones[6])
def __init__(self, *args,**kwargs):
super(RowerHRZonesForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
hrzones = kwargs['instance'].hrzones
else:
hrzones = ['Rest','UT2','UT1','AT','TR','AN','Max']
self.fields['hrrestname'].initial = hrzones[0]
self.fields['hrut2name'].initial = hrzones[1]
self.fields['hrut1name'].initial = hrzones[2]
self.fields['hratname'].initial = hrzones[3]
self.fields['hrtrname'].initial = hrzones[4]
self.fields['hranname'].initial = hrzones[5]
self.fields['hrmaxname'].initial = hrzones[6]
class Meta:
model = Rower
fields = ['rest','ut2','ut1','at','tr','an','max']
def clean(self):
cleaned_data = super(RowerHRZonesForm, self).clean()
try:
rest = cleaned_data['rest']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
rest = int(self.data['rest'])
try:
ut2 = cleaned_data['ut2']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
ut2 = int(self.data['ut2'])
try:
ut1 = cleaned_data['ut1']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
ut1 = int(self.data['ut1'])
try:
at = cleaned_data['at']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
at = int(self.data['at'])
try:
tr = cleaned_data['tr']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
tr = int(self.data['tr'])
try:
an = cleaned_data['an']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
an = int(self.data['an'])
try:
max = cleaned_data['max']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
max = int(self.data['max'])
try:
hrrestname = cleaned_data['hrrestname']
except:
hrrestname = 'Rest'
cleaned_data['hrut3name'] = 'Rest'
try:
hrut2name = cleaned_data['hrut2name']
except:
hrut2name = 'UT2'
cleaned_data['hrut2name'] = 'UT2'
try:
hrut1name = cleaned_data['hrut1name']
except:
hrut1name = 'UT1'
cleaned_data['hrut1name'] = 'UT1'
try:
hratname = cleaned_data['hratname']
except:
hratname = 'AT'
cleaned_data['hratname'] = 'AT'
try:
hrtrname = cleaned_data['hrtrname']
except:
hrtrname = 'TR'
cleaned_data['hrtrname'] = 'TR'
try:
hranname = cleaned_data['hranname']
except:
hranname = 'AN'
cleaned_data['hranname'] = 'AN'
if rest >= ut2:
e = "{ut2name} should be higher than {restname}".format(
restname=hrrestname,
ut2name=hrut2name
)
raise forms.ValidationError(e)
if ut1 <= ut2:
e = "{ut1name} should be higher than {ut2name}".format(
ut1name = hrut1name,
ut2name= hrut2name,
)
raise forms.ValidationError(e)
if at <= ut1:
e = "{atname} should be higher than {ut1name}".format(
atname = hratname,
ut1name= hrut1name,
)
raise forms.ValidationError(e)
if tr <= at:
e = "{trname} should be higher than {atname}".format(
atname = hratname,
trname= hrtrname,
)
raise forms.ValidationError(e)
if an <= tr:
e = "{anname} should be higher than {trname}".format(
anname = hranname,
trname= hrtrname,
)
raise forms.ValidationError(e)
if max <= an:
e = "{anname} should be lower than {maxname}".format(
anname=hranname,
maxname=hrmaxname,
)
return cleaned_data
# Form to set rower's Power zones, including test routines
# to enable consistency
class RowerPowerZonesForm(ModelForm):
@@ -3905,12 +3748,44 @@ class RowerPowerZonesForm(ModelForm):
trname = cleaned_data['trname']
except:
trname = 'TR'
cleaned_data['trname'] = 'TR'
cleaned_data['ut1name'] = 'TR'
try:
anname = cleaned_data['anname']
except:
anname = 'AN'
cleaned_data['anname'] = 'AN'
cleaned_data['ut1name'] = 'AN'
try:
ut3name = cleaned_data['ut3name']
except:
ut2name = 'UT3'
cleaned_data['ut3name'] = 'UT3'
try:
ut2name = cleaned_data['ut2name']
except:
ut2name = 'UT2'
cleaned_data['ut2name'] = 'UT2'
try:
ut1name = cleaned_data['ut1name']
except:
ut1name = 'UT1'
cleaned_data['ut1name'] = 'UT1'
try:
atname = cleaned_data['atname']
except:
atname = 'AT'
cleaned_data['atname'] = 'AT'
try:
trname = cleaned_data['trname']
except:
trname = 'TR'
cleaned_data['ut1name'] = 'TR'
try:
anname = cleaned_data['anname']
except:
anname = 'AN'
cleaned_data['ut1name'] = 'AN'
if pw_ut1 <= pw_ut2:

214
rowers/rp3stuff.py Normal file
View File

@@ -0,0 +1,214 @@
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
# All the functionality needed to connect to Runkeeper
from rowers.imports import *
# Python
import gzip
import base64
from io import BytesIO
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
rp3_CLIENT_ID, rp3_CLIENT_SECRET,
rp3_REDIRECT_URI,rp3_CLIENT_KEY,
RP3_CLIENT_ID, RP3_CLIENT_KEY, RP3_REDIRECT_URI, RP3_CLIENT_SECRET
)
tpapilocation = "https://api.trainingpeaks.com"
from celery import Celery,app
from django_rq import job
import time
#from async_messages import message_user,messages
oauth_data = {
'client_id': RP3_CLIENT_ID,
'client_secret': RP3_CLIENT_SECRET,
'redirect_uri': RP3_REDIRECT_URI,
'autorization_uri': "https://rp3rowing-app.com/oauth/authorize?",
'content_type': 'application/x-www-form-urlencoded',
# 'content_type': 'application/json',
'tokenname': 'rp3token',
'refreshtokenname': 'rp3refreshtoken',
'expirydatename': 'rp3tokenexpirydate',
'bearer_auth': False,
'base_url': "https://rp3rowing-app.com/oauth/token",
'scope':'read,write',
}
from rowers.rower_rules import is_workout_user
# Checks if user has UnderArmour token, renews them if they are expired
def rp3_open(user):
return imports_open(user, oauth_data)
# Refresh ST token using refresh token
def do_refresh_token(refreshtoken):
return imports_do_refresh_token(refreshtoken, oauth_data)
# Exchange access code for long-lived access token
def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(rp3_CLIENT_KEY, rp3_CLIENT_SECRET)
post_data = {
"client_id":rp3_CLIENT_KEY,
"grant_type": "authorization_code",
"code": code,
"redirect_uri":rp3_REDIRECT_URI,
"client_secret": rp3_CLIENT_SECRET,
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
response = requests.post(
"https://oauth.trainingpeaks.com/oauth/token",
data=post_data,verify=False,
)
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
expires_in = 0
refresh_token = 0
return thetoken,expires_in,refresh_token
# Make authorization URL including random string
def make_authorization_url(request):
return imports_make_authorization_url(oauth_data)
def getidfromresponse(response):
t = json.loads(response.text)
links = t["_links"]
id = links["self"][0]["id"]
return int(id)
def createtpworkoutdata(w):
filename = w.csvfilename
row = rowingdata(csvfile=filename)
tcxfilename = filename[:-4]+'.tcx'
try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
row.exporttotcx(tcxfilename,notes=newnotes)
return tcxfilename
def rp3_check(access_token):
headers = {
"Content-Type": "application/json",
'Accept': 'application/json',
'authorization': 'Bearer %s' % access_token
}
resp = requests.post(tpapilocation+"/v1/info/version",
headers=headers,verify=False)
return resp
def uploadactivity(access_token,filename,description='',
name='Rowsandall.com workout'):
data_gz = BytesIO()
with open(filename,'rb') as inF:
s = inF.read()
with gzip.GzipFile(fileobj=data_gz,mode="w") as gzf:
gzf.write(s)
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer %s' % access_token
}
data = {
"UploadClient": "rowsandall",
"Filename": filename,
"SetWorkoutPublic": True,
"Title":name,
"Type": "rowing",
"Comment": description,
"Data": base64.b64encode(data_gz.getvalue()).decode("ascii")
}
resp = requests.post(tpapilocation+"/v1/file",
data = json.dumps(data),
headers=headers,verify=False)
if resp.status_code != 200:
return 0,resp.reason,resp.status_code,headers
else:
return resp.json()[0]["Id"],"ok",200,""
return 0,0,0,0
def workout_rp3_upload(user,w):
message = "Uploading to TrainingPeaks"
tpid = 0
r = w.user
thetoken = rp3_open(r.user)
# need some code if token doesn't refresh
if (is_workout_user(user,w)):
tcxfile = createtpworkoutdata(w)
if tcxfile:
res,reason,status_code,headers = uploadactivity(
thetoken,tcxfile,
name=w.name
)
if res == 0:
message = "Upload to TrainingPeaks failed with status code "+str(status_code)+": "+reason
w.tpid = -1
try:
os.remove(tcxfile)
except WindowsError:
pass
return message,tpid
else: # res != 0
w.uploadedtotp = res
tpid = res
w.save()
os.remove(tcxfile)
return 'Successfully synchronized to TrainingPeaks',tpid
else: # no tcxfile
message = "Upload to TrainingPeaks failed"
w.uploadedtotp = -1
tpid = -1
w.save()
return message,tpid
else: # not allowed to upload
message = "You are not allowed to export this workout to TP"
tpid = 0
return message,tpid
return message,tpid

View File

@@ -859,7 +859,34 @@ def rower_process_underarmourcallback(request):
url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url)
# Process RP3 callback
@login_required()
def rower_process_rp3callback(request):
try:
code = request.GET['code']
except MultiValueDictKeyError:
messages.error(request,"There was an error with the callback")
try:
errormessage = request.GET['error']
messages.error(request,errormessage)
except MultiValueDictKeyError:
pass
url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url)
res = tpstuff.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.tptoken = access_token
r.tptokenexpirydate = expirydatetime
r.tprefreshtoken = refresh_token
r.save()
# Process TrainingPeaks callback
@login_required()

View File

@@ -321,6 +321,12 @@ TP_CLIENT_SECRET = CFG["tp_client_secret"]
TP_REDIRECT_URI = CFG["tp_redirect_uri"]
TP_CLIENT_KEY = TP_CLIENT_ID
# RP3
RP3_CLIENT_ID = CFG["rp3_client_id"]
RP3_CLIENT_SECRET = CFG["rp3_client_secret"]
RP3_REDIRECT_URI = CFG["rp3_redict_uri"]
RP3_CLIENT_KEY = RP3_CLIENT_ID
# Full Site URL
SITE_URL = CFG['site_url']

View File

@@ -77,6 +77,7 @@ urlpatterns += [
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),
re_path(r'^rp3\_callback',rowersviews.rower_process_rp3callback),
re_path(r'^twitter\_callback',rowersviews.rower_process_twittercallback),
re_path(r'^i18n/', include('django.conf.urls.i18n')),
re_path(r'^tz_detect/', include('tz_detect.urls')),