rp3 in progress
This commit is contained in:
203
rowers/models.py
203
rowers/models.py
@@ -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
214
rowers/rp3stuff.py
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -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')),
|
||||
|
||||
Reference in New Issue
Block a user